/*
 * fbasync - An Asynchronous Facebook API for .NET
 * Copyright (C) 2007 ittybittysoft
 * Email: info@ittybittysoft.com
 * Portions of the library are based on the Facebook Developer Toolkit
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using fbasync.Exceptions;
using System.Web;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.IO.Compression;
using System.Runtime.Serialization.Json;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using System.Diagnostics;

namespace fbasync
{
    public enum APIModes
    {
        Facebook = 0,
        Bebo = 1
    }

    /// <summary>
    /// The type of notification that is sent to a user
    /// </summary>
    public enum NotificationTypes
    {
        /// <summary>
        /// General notifications are from one user to another.
        /// </summary>
        General = 0,

        /// <summary>
        /// Notifications from your application to a user. No sending user session is required.
        /// </summary>
        Announcement = 1
    }

    public enum AssociationTypes
    {
        OneWay = 1,
        TwoWaySymmetric = 2,
        TwoWayAsymmetric = 3
    }

    /// <summary>
    /// Facebook grants you allocations to various user notification features. These are the ones you can query.
    /// </summary>
    public enum AllocationType
        : int
    {
        /// <summary>
        /// The number of notifications your application can send on behalf of a user per day. These are user-to-user notifications.
        /// </summary>
        notifications_per_day = 0,
        /// <summary>
        /// The number of notifications your application can send to a user per week. These are application-to-user notifications.
        /// </summary>
        announcement_notifications_per_week = 1,
        /// <summary>
        /// The number of requests your application can send on behalf of a user per day.
        /// </summary>
        requests_per_day = 2,
        /// <summary>
        /// The number of email messages your application can send to a user per day.
        /// </summary>
        emails_per_day = 3,
        /// <summary>
        /// The location of the disable message within emails sent by your application. '1' is the bottom of the message and '2' is the top of the message.
        /// </summary>
        email_disable_message_location = 4,
    }

    [Serializable()]
    public partial class FacebookConnection
        : ISerializable
    {
        const string SendNotificationDepricatedMsg = "The method is depricated. Use dashboard api instead.";

        private static APIModes? _apiMode;
        public static APIModes APIMode
        {
            get
            {
                if (null == _apiMode)
                {
                    string configValue = System.Configuration.ConfigurationManager.AppSettings.Get("fbasync.APIMode");
                    _apiMode = string.IsNullOrEmpty(configValue) ? APIModes.Facebook : (APIModes)Enum.Parse(typeof(APIModes), configValue, true);
                }

                return _apiMode.Value;
            }
            set { _apiMode = value; }
        }

        internal static string Version = @"1.0";
        internal static string FacebookRestUrl { get { return APIMode == APIModes.Facebook ? @"http://api.facebook.com/restserver.php" : @"http://apps.bebo.com/restserver.php"; } }
        internal static string FacebookSecureRestUrl { get { return APIMode == APIModes.Facebook ? @"https://api.facebook.com/restserver.php" : @"https://apps.bebo.com/restserver.php"; } }
        internal static string FacebookNamespace { get { return APIMode == APIModes.Facebook ? @"http://api.facebook.com/1.0/" : @"http://api.bebo.com/1.0/"; } }
        public static string MissingPictureUrl { get { return APIMode == APIModes.Facebook ? @"http://static.ak.facebook.com/pics/s_default.jpg" : @"http://s.bebo.com/img/nopic/MB_M_ml.gif"; } }
        internal static string LoginRedirect { get { return APIMode == APIModes.Facebook ? @"http://www.facebook.com/login.php?api_key={0}&v=1.0&next={1}" : @"http://www.bebo.com/SignIn.jsp?ApiKey={0}&v=1.0&next={1}"; } }
        internal static string LoginRedirectNoNext { get { return APIMode == APIModes.Facebook ? @"http://www.facebook.com/login.php?api_key={0}&v=1.0" : @"http://www.bebo.com/SignIn.jsp?ApiKey={0}&v=1.0"; } }

        public static string GetLoginRedirectUrl(string apiKey, string nextUrl)
        {
            if (null == apiKey) throw new ArgumentNullException("apiKey");

            if (null == nextUrl)
                return string.Format(LoginRedirectNoNext, HttpUtility.UrlEncode(apiKey));
            else
                return string.Format(LoginRedirect, HttpUtility.UrlEncode(apiKey), HttpUtility.UrlEncode(nextUrl));
        }

        public event EventHandler<AsyncCompletedEventArgs<IList<Album>>> GetAlbumsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<User>>> GetFriendsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<User>>> GetUsersCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<Photo>>> GetPhotosCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<Photo>>> GetGroupPhotosCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<Photo>>> GetEventPhotosCompleted;
        public event EventHandler<AsyncCompletedEventArgs> GetSessionCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<Event>>> GetEventsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<Group>>> GetGroupsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<XmlDocument>> ExecuteFqlCompleted;
        public event EventHandler<AsyncCompletedEventArgs<XmlDocument>> ExecuteApiCallCompleted;
        public event EventHandler<AsyncCompletedEventArgs> SetProfileCompleted;
        public event EventHandler<AsyncCompletedEventArgs> PublishTemplatizedActionCompleted;
        public event EventHandler<AsyncCompletedEventArgs> PublishStoryToUserCompleted;
        public event EventHandler<AsyncCompletedEventArgs> PublishActionOfUserCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<long>>> SendNotificationCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<long>>> SendNotificationEmailCompleted;
        public event EventHandler<AsyncCompletedEventArgs> SendSmsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> CanSendSmsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> PublishUserActionCompleted;
        public event EventHandler<AsyncCompletedEventArgs<long>> RegisterTemplateBundleCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> DeactivateTemplateBundleCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<StoryTemplateBundle>>> GetRegisteredTemplateBundlesCompleted;
        public event EventHandler<AsyncCompletedEventArgs<StoryTemplateBundle>> GetRegisteredTemplateBundleCompleted;
        public event EventHandler<AsyncCompletedEventArgs> SetProfileInfoCompleted;
        public event EventHandler<AsyncCompletedEventArgs> SetProfileInfoOptionsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<InfoSection>> GetProfileInfoSectionCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<InfoItem>>> GetProfileInfoOptionsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> SetUserPreferenceCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> DefineAssociationCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> CreateObjectTypeCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<ExtendedPermissions>>> GetExtendedPermissionsCompleted;
        public event EventHandler<AsyncCompletedEventArgs> RevokeExtendedPermissionCompleted;
        public event EventHandler<AsyncCompletedEventArgs<long>> AdminGetAllocationCompleted;
        public event EventHandler<AsyncCompletedEventArgs<IList<long>>> GetFriendIDsCompleted;
        public event EventHandler<AsyncCompletedEventArgs<long>> PublishToStreamCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> IsAppUserCompleted;
        public event EventHandler<AsyncCompletedEventArgs<bool>> IsFanCompleted;

        public FacebookConnection()
        {
            ApplicationKey = string.Empty;
            AuthToken = string.Empty;

            ApplicationKey = System.Configuration.ConfigurationManager.AppSettings.Get("fbasync.FacebookApiKey") ?? string.Empty;
            Secret = System.Configuration.ConfigurationManager.AppSettings.Get("fbasync.FacebookSecret");
        }

        public string ApplicationKey { get; set; }
        public string Secret { get; set; }
        public string SessionKey { get; set; }
        public string AuthToken { get; set; }
        public long? UserId { get; set; }
        public double SessionExpiresUnixTime { get; set; }
        public DateTime SessionExpiresDateTime { get { return SessionExpiresUnixTime.FromUnixTime(); } }
        public bool SessionExpires { get { return SessionExpiresDateTime > Utility.UnixStart; } }

        #region Get Albums
        public void GetAlbumsAsync()
        {
            AssertUserId();
            GetAlbumsAsync(Album.AllFields, "owner=" + this.UserId.Value.ToString(), Album.DefaultOrderBy, null);
        }

        public void GetAlbumsAsync(string where)
        {
            GetAlbumsAsync(Album.AllFields, where, Album.DefaultOrderBy, null);
        }

        public void GetAlbumsAsync(string where, string orderBy)
        {
            GetAlbumsAsync(Album.AllFields, where, orderBy, null);
        }

        public void GetAlbumsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy)
        {
            GetAlbumsAsync(fieldsToLoad, where, orderBy, null);
        }

        public void GetAlbumsAsync(object userState)
        {
            AssertUserId();
            GetAlbumsAsync(Album.AllFields, "owner=" + this.UserId.Value.ToString(), Album.DefaultOrderBy, userState);
        }

        public void GetAlbumsAsync(string where, object userState)
        {
            GetAlbumsAsync(Album.AllFields, where, Album.DefaultOrderBy, userState);
        }

        public void GetAlbumsAsync(string where, string orderBy, object userState)
        {
            GetAlbumsAsync(Album.AllFields, where, orderBy, userState);
        }

        public void GetAlbumsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetAlbums(
                fieldsToLoad,
                where,
                orderBy,
                (ar) => { SetupAsyncCompletion<AlbumState, IList<Album>>(ar, EndGetAlbums, OnGetAlbumsCompleted); },
                op);
        }

        public IAsyncResult BeginGetAlbums(AsyncCallback callback, object state)
        {
            AssertUserId();
            return BeginGetAlbums(Album.AllFields, "owner=" + this.UserId.Value.ToString(), Album.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetAlbums(string where, AsyncCallback callback, object state)
        {
            return BeginGetAlbums(Album.AllFields, where, Album.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetAlbums(string where, string orderBy, AsyncCallback callback, object state)
        {
            return BeginGetAlbums(Album.AllFields, where, orderBy, callback, state);
        }

        public IAsyncResult BeginGetAlbums(IEnumerable<string> fieldsToLoad, string where, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<AlbumState> myAr = new LazyAsyncResult<AlbumState>(callback, state);
            string query = BuildFqlQuery(fieldsToLoad, Album.FacebookEntity, where, orderBy);
            BeginExecuteFql(query, false, AlbumsCallback, myAr);
            return myAr;
        }

        protected virtual void AlbumsCallback(IAsyncResult ar)
        {
            ProcessFqlResult<AlbumState>(ar,
                (albumXml, myAr) =>
                myAr.MyAsyncState.Results = Album.FromFqlResult(albumXml, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetAlbumsCompleted(object sender, AsyncCompletedEventArgs<IList<Album>> e)
        {
            var eh = GetAlbumsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<Album> EndGetAlbums(IAsyncResult ar)
        {
            AlbumState myState = ar.End<AlbumState>();

            return myState.Results;
        }
        #endregion

        #region Get Extended Permissions
        public void GetExtendedPermissionsAsync()
        {
            AssertUserId();
            GetExtendedPermissionsAsync(ExtendedPermissions.AllFields, "uid=" + this.UserId.Value.ToString(), ExtendedPermissions.DefaultOrderBy, null);
        }

        public void GetExtendedPermissionsAsync(long uid)
        {
            GetExtendedPermissionsAsync(ExtendedPermissions.AllFields, "uid=" + uid, ExtendedPermissions.DefaultOrderBy, null);
        }

        public void GetExtendedPermissionsAsync(IEnumerable<long> uids)
        {
            GetExtendedPermissionsAsync(ExtendedPermissions.AllFields, "uid in (" + string.Join(",", uids.Select(u => u.ToString()).ToArray()) + ")", ExtendedPermissions.DefaultOrderBy, null);
        }

        public void GetExtendedPermissionsAsync(object userState)
        {
            AssertUserId();
            GetExtendedPermissionsAsync(ExtendedPermissions.AllFields, "uid=" + this.UserId.Value.ToString(), ExtendedPermissions.DefaultOrderBy, userState);
        }

        public void GetExtendedPermissionsAsync(long uid, object userState)
        {
            GetExtendedPermissionsAsync(ExtendedPermissions.AllFields, "uid=" + uid, ExtendedPermissions.DefaultOrderBy, userState);
        }

        public void GetExtendedPermissionsAsync(IEnumerable<long> uids, object userState)
        {
            GetExtendedPermissionsAsync(ExtendedPermissions.AllFields, "uid in (" + string.Join(",", uids.Select(u => u.ToString()).ToArray()) + ")", ExtendedPermissions.DefaultOrderBy, userState);
        }

        public void GetExtendedPermissionsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetExtendedPermissions(
                fieldsToLoad,
                where,
                orderBy,
                (ar) => { SetupAsyncCompletion<ExtendedPermissionsState, IList<ExtendedPermissions>>(ar, EndGetExtendedPermissions, OnGetExtendedPermissionsCompleted); },
                op);
        }

        public IAsyncResult BeginGetExtendedPermissions(AsyncCallback callback, object state)
        {
            AssertUserId();
            return BeginGetExtendedPermissions(ExtendedPermissions.AllFields, "uid=" + this.UserId.Value.ToString(), ExtendedPermissions.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetExtendedPermissions(long uid, AsyncCallback callback, object state)
        {
            return BeginGetExtendedPermissions(ExtendedPermissions.AllFields, "uid=" + uid, ExtendedPermissions.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetExtendedPermissions(IEnumerable<long> uids, AsyncCallback callback, object state)
        {
            return BeginGetExtendedPermissions(ExtendedPermissions.AllFields, "uid in (" + string.Join(",", uids.Select(u => u.ToString()).ToArray()) + ")", ExtendedPermissions.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetExtendedPermissions(IEnumerable<string> fieldsToLoad, string where, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<ExtendedPermissionsState> myAr = new LazyAsyncResult<ExtendedPermissionsState>(callback, state);
            string query = BuildFqlQuery(fieldsToLoad, ExtendedPermissions.FacebookEntity, where, orderBy);
            BeginExecuteFql(query, false, false, ExtendedPermissionsCallback, myAr);
            return myAr;
        }

        protected virtual void ExtendedPermissionsCallback(IAsyncResult ar)
        {
            ProcessFqlResult<ExtendedPermissionsState>(ar,
                (extendedPermissionXml, myAr) =>
                myAr.MyAsyncState.Results = ExtendedPermissions.FromFqlResult(extendedPermissionXml, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetExtendedPermissionsCompleted(object sender, AsyncCompletedEventArgs<IList<ExtendedPermissions>> e)
        {
            var eh = GetExtendedPermissionsCompleted;

            if (null != eh)
                eh(sender, e);
        }

        public IList<ExtendedPermissions> EndGetExtendedPermissions(IAsyncResult ar)
        {
            ExtendedPermissionsState myState = ar.End<ExtendedPermissionsState>();

            return myState.Results;
        }

        public void RevokeExtendedPermissionAsync(long uid, ExtendedPermission perm, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginRevokeExtendedPermission(
                uid,
                perm,
                (ar) => { SetupAsyncCompletion<object>(ar, EndRevokeExtendedPermission, OnRevokeExtendedPermissionCompleted); },
                op);
        }

        protected virtual void OnRevokeExtendedPermissionCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = RevokeExtendedPermissionCompleted;

            if (null != eh)
                eh(sender, e);
        }

        public IAsyncResult BeginRevokeExtendedPermission(long uid, ExtendedPermission perm, AsyncCallback callback, object state)
        {
            LazyAsyncResult<object> myAr = new LazyAsyncResult<object>(callback, state);

            Dictionary<string, string> parameterList = new Dictionary<string, string>();
            parameterList.Add("method", "auth.revokeExtendedPermission");
            parameterList.Add("perm", perm.ToString());
            parameterList.Add("uid", uid.ToString());

            BeginExecuteApiCall(parameterList, false, true, ObjectStateCallback, myAr);

            return myAr;
        }

        public void EndRevokeExtendedPermission(IAsyncResult ar)
        {
            ar.End<object>();
        }

        #endregion

        #region Set Profile

        public void SetProfileAsync(long? uid, string profileFbml, object userState)
        {
            SetProfileAsync(uid, profileFbml, string.Empty, string.Empty, null, userState);
        }

        public void SetProfileAsync(long? uid, string profileFbml, string profileMainFbml, string mobileProfileFbml, object userState)
        {
            SetProfileAsync(uid, profileFbml, profileMainFbml, mobileProfileFbml, null, userState);
        }

        public void SetProfileAsync(long? uid, string profileFbml, string profileMainFbml, string mobileProfileFbml, string sessionKey, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSetProfile(
                uid,
                profileFbml,
                profileMainFbml,
                mobileProfileFbml,
                sessionKey,
                (ar) => { SetupAsyncCompletion<object>(ar, EndSetProfile, OnSetProfileCompleted); },
                op);
        }

        public IAsyncResult BeginSetProfile(long? uid, string profileFbml, AsyncCallback callback, object state)
        {
            return BeginSetProfile(uid, profileFbml, string.Empty, string.Empty, null, callback, state);
        }

        public IAsyncResult BeginSetProfile(long? uid, string profileFbml, string profileMainFbml, string mobileProfileFbml, AsyncCallback callback, object state)
        {
            return BeginSetProfile(uid, profileFbml, profileMainFbml, mobileProfileFbml, null, callback, state);
        }

        public IAsyncResult BeginSetProfile(long? uid, string profileFbml, string profileMainFbml, string mobileProfileFbml, string sessionKey, AsyncCallback callback, object state)
        {
            LazyAsyncResult<object> myAr = new LazyAsyncResult<object>(callback, state);

            var args = new Dictionary<string, string>();
            if (uid.HasValue)
                args.Add("uid", uid.Value.ToString());

            if (!string.IsNullOrEmpty(sessionKey))
                args.Add("session_key", sessionKey);

            //Bebo uses old Facebook "infinite session key" approach
            if (APIMode == APIModes.Bebo)
            {
                args.Add("markup", profileFbml);
                BeginExecuteApiCall("profile.setFBML", args, true, false, ObjectStateCallback, myAr);
            }
            else if (APIMode == APIModes.Facebook)
            {
                args.Add("profile", profileFbml);
                args.Add("profile_main", profileMainFbml);
                args.Add("mobile_profile", mobileProfileFbml);
                BeginExecuteApiCall("profile.setFBML", args, !uid.HasValue, false, ObjectStateCallback, myAr);
            }
            else
            {
                throw new InvalidOperationException("Unhandled APIMode: " + APIMode.ToString());
            }

            return myAr;
        }

        protected virtual void OnSetProfileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = SetProfileCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public void EndSetProfile(IAsyncResult ar)
        {
            ar.End<object>();
        }
        #endregion

        #region Publish Templatized Action
        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishTemplatizedActionAsync(string titleTemplate, IDictionary<string, string> titleData, string bodyTemplate, IDictionary<string, string> bodyData, string bodyGeneral, long? pageActorId, IDictionary<string, string> images, IList<long> targetIds, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginPublishTemplatizedAction(
                titleTemplate,
                titleData,
                bodyTemplate,
                bodyData,
                bodyGeneral,
                pageActorId,
                images,
                targetIds,
                (ar) => { SetupAsyncCompletion<LongListState>(ar, EndPublishTemplatizedAction, OnPublishTemplatizedActionCompleted); },
                op);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishTemplatizedActionAsync(string titleTemplate, IDictionary<string, string> titleData, string bodyTemplate, IDictionary<string, string> bodyData, string bodyGeneral, IDictionary<string, string> images, object userState)
        {
            PublishTemplatizedActionAsync(titleTemplate, titleData, bodyTemplate, bodyData, bodyGeneral, null, images, null, userState);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishTemplatizedActionAsync(string titleTemplate, object userState)
        {
            PublishTemplatizedActionAsync(titleTemplate, null, null, null, null, null, null, null, userState);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishTemplatizedAction(string titleTemplate, IDictionary<string, string> titleData, string bodyTemplate, IDictionary<string, string> bodyData, string bodyGeneral, long? pageActorId, IDictionary<string, string> images, IList<long> targetIds, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            if (string.IsNullOrEmpty(titleTemplate)) throw new ArgumentNullException("titleTemplate");
            if (null != images && images.Count > 4) throw new ArgumentException("You can only specify up to four images.");

            var args = new Dictionary<string, string>();

            args.Add("title_template", titleTemplate);

            if (null != titleData && titleData.Count > 0)
                args.Add("title_data", titleData.ToJsonAssociativeArray());

            if (!string.IsNullOrEmpty(bodyTemplate))
                args.Add("body_template", bodyTemplate);

            if (null != bodyData && bodyData.Count > 0)
                args.Add("body_data", bodyData.ToJsonAssociativeArray());

            if (!string.IsNullOrEmpty(bodyGeneral))
                args.Add("body_general", bodyGeneral);

            if (null != pageActorId)
                args.Add("page_actor_id", pageActorId.Value.ToString());

            args.AddImages(images);

            if (null != targetIds && targetIds.Count > 0)
                args.Add("target_ids", string.Join(",", targetIds.Select(i => i.ToString()).ToArray()));

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall(@"feed.publishTemplatizedAction", args, true, false, LongListCallback, myAr);
            return myAr;
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishTemplatizedAction(string titleTemplate, IDictionary<string, string> titleData, string bodyTemplate, IDictionary<string, string> bodyData, string bodyGeneral, IDictionary<string, string> images, AsyncCallback callback, object state)
        {
            return BeginPublishTemplatizedAction(titleTemplate, titleData, bodyTemplate, bodyData, bodyGeneral, null, images, null, callback, state);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishTemplatizedAction(string titleTemplate, AsyncCallback callback, object state)
        {
            return BeginPublishTemplatizedAction(titleTemplate, null, null, null, null, null, null, null, callback, state);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        protected virtual void OnPublishTemplatizedActionCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = PublishTemplatizedActionCompleted;

            if (null != eh)
                eh(sender, e);
        }


        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void EndPublishTemplatizedAction(IAsyncResult ar)
        {
            LongListState myState = ar.End<LongListState>();
            if (myState.Results.Count == 0 || myState.Results[0] != 1)
                throw new FacebookException("Error publishing item");
        }
        #endregion

        #region Publish Story To User
        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishStoryToUserAsync(string title, string body, IDictionary<string, string> images, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginPublishStoryToUser(
                title,
                body,
                images,
                (ar) => { SetupAsyncCompletion<LongListState>(ar, EndPublishStoryToUser, OnPublishStoryToUserCompleted); },
                op);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishStoryToUserAsync(string title, object userState)
        {
            PublishStoryToUserAsync(title, null, null, userState);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishStoryToUserAsync(string title, string body, object userState)
        {
            PublishStoryToUserAsync(title, body, null, userState);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishStoryToUser(string title, string body, IDictionary<string, string> images, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            if (string.IsNullOrEmpty(title)) throw new ArgumentNullException("title");

            if (null != images && images.Count > 4)
                throw new ArgumentException("You can only specify up to four images.");

            var args = new Dictionary<string, string>();

            args.Add("title", title);

            if (!string.IsNullOrEmpty(body))
                args.Add("body", body);

            args.AddImages(images);

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall(@"feed.publishStoryToUser", args, true, false, LongListCallback, myAr);
            return myAr;
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishStoryToUser(string title, AsyncCallback callback, object state)
        {
            return BeginPublishStoryToUser(title, null, null, callback, state);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishStoryToUser(string title, string body, AsyncCallback callback, object state)
        {
            return BeginPublishStoryToUser(title, body, null, callback, state);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        protected virtual void OnPublishStoryToUserCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = PublishStoryToUserCompleted;

            if (null != eh)
                eh(sender, e);
        }


        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void EndPublishStoryToUser(IAsyncResult ar)
        {
            LongListState myState = ar.End<LongListState>();
            if (myState.Results.Count == 0 || myState.Results[0] != 1)
                throw new FacebookException("Error publishing item");
        }
        #endregion

        #region Publish Action Of User
        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishActionOfUserAsync(string title, string body, IDictionary<string, string> images, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginPublishActionOfUser(
                title,
                body,
                images,
                (ar) => { SetupAsyncCompletion<LongListState>(ar, EndPublishActionOfUser, OnPublishActionOfUserCompleted); },
                op);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishActionOfUserAsync(string title, object userState)
        {
            PublishActionOfUserAsync(title, null, null, userState);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void PublishActionOfUserAsync(string title, string body, object userState)
        {
            PublishActionOfUserAsync(title, body, null, userState);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishActionOfUser(string title, string body, IDictionary<string, string> images, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            if (string.IsNullOrEmpty(title)) throw new ArgumentNullException("title");

            if (null != images && images.Count > 4)
                throw new ArgumentException("You can only specify up to four images.");

            var args = new Dictionary<string, string>();

            args.Add("title", title);

            if (!string.IsNullOrEmpty(body))
                args.Add("body", body);

            args.AddImages(images);

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall(@"feed.publishActionOfUser", args, true, false, LongListCallback, myAr);
            return myAr;
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishActionOfUser(string title, AsyncCallback callback, object state)
        {
            return BeginPublishActionOfUser(title, null, null, callback, state);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public IAsyncResult BeginPublishActionOfUser(string title, string body, AsyncCallback callback, object state)
        {
            return BeginPublishActionOfUser(title, body, null, callback, state);
        }

        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        protected virtual void OnPublishActionOfUserCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = PublishActionOfUserCompleted;

            if (null != eh)
                eh(sender, e);
        }


        [Obsolete("This is deprecated by Facebook (but not Bebo). Use PublishUserAction methods for Facebook.")]
        public void EndPublishActionOfUser(IAsyncResult ar)
        {
            LongListState myState = ar.End<LongListState>();
            if (myState.Results.Count == 0 || myState.Results[0] != 1)
                throw new FacebookException("Error publishing item");
        }
        #endregion

        #region Publish to stream

        public void PublishToStreamAsync(string message, Attachment attachment, IList<ActionLink> action_links, long target_id, long uid, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginPublishToStream(
                message,
                attachment,
                action_links,
                target_id,
                uid,
                (ar) => { SetupAsyncCompletion<LongListState, long>(ar, EndPublishToStream, OnPublishToStreamCompleted); },
                op);
        }

        protected virtual void OnPublishToStreamCompleted(object sender, AsyncCompletedEventArgs<long> e)
        {
            var eh = PublishToStreamCompleted;

            if (null != eh)
                eh(sender, e);
        }

        /// <summary>Publishes a post into the stream on the Wall of a user or a Facebook Page, group, or event connected to the user. <remarks>Use this overload if a desktop applicatons calls stream.publish. Requires active session being set.</remarks></summary>
        /// <exception cref="InvalidOperationException">An active session is required.</exception>
        public IAsyncResult BeginPublishToStream(string message, Attachment attachment, IList<ActionLink> action_links, long target_id, AsyncCallback callback, object state)
        {
            AssertActiveSession();
            return BeginPublishToStream(message, attachment, action_links, target_id, default(long), callback, state);
        }

        public IAsyncResult BeginPublishToStream(string message, Attachment attachment, IList<ActionLink> action_links, long target_id, long uid, AsyncCallback callback, object state)
        {
            if (String.IsNullOrEmpty(message)) throw new ArgumentNullException("message");

            var args = new Dictionary<string, string>();
            args.Add("message", message);
            args.Add("target_id", target_id.ToString());

            bool useSession = uid == default(long);
            if (!useSession)
                args.Add("uid", uid.ToString());

            if (attachment != null)
            {
                args.Add("attachment", attachment.ToJson());
            }

            if (action_links != null && action_links.Count > 0)
            {
                var lst = new List<string>(action_links.Count);

                foreach (var al in action_links)
                {
                    var dict = new Dictionary<string, string>(2);
                    dict.Add("text", al.text);
                    dict.Add("href", al.href);

                    lst.Add(dict.ToJsonAssociativeArray());
                }

                args.Add("action_links", lst.ToJsonArray());
            }

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall(@"stream.publish", args, useSession, false, LongListCallbackWithUnderscores, myAr);
            return myAr;
        }

        /// <exception cref="FacebookException"></exception>
        public long EndPublishToStream(IAsyncResult ar)
        {
            LongListState myState = ar.End<LongListState>();
            if (myState.Results.Count == 0) throw new FacebookException("Error publishing to stream.");
            return myState.Results.Last();
        }

        #endregion

        #region Send SMS
        public void SendSmsAsync(long uid, string message, int? sessionId, bool requestSession, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSendSms(
                uid,
                message,
                sessionId,
                requestSession,
                (ar) => { SetupAsyncCompletion<object>(ar, EndSendSms, OnSendSmsCompleted); },
                op);
        }

        public void SendSmsAsync(long uid, string message, int sessionID, object userState)
        {
            SendSmsAsync(uid, message, sessionID, false, userState);
        }

        public void SendSmsAsync(long uid, string message, bool requestSession, object userState)
        {
            SendSmsAsync(uid, message, null, requestSession, userState);
        }

        public IAsyncResult BeginSendSms(long uid, string message, int? sessionId, bool requestSession, AsyncCallback callback, object state)
        {
            if (string.IsNullOrEmpty(message)) throw new ArgumentNullException("message");

            var args = new Dictionary<string, string>();

            args.Add("uid", uid.ToString());
            args.Add("message", message);

            if (null != sessionId)
                args.Add("session_id", sessionId.Value.ToString());

            if (requestSession)
                args.Add("req_session", "1");

            LazyAsyncResult<object> myAr = new LazyAsyncResult<object>(callback, state);
            BeginExecuteApiCall(@"sms.send", args, false, false, ObjectStateCallback, myAr);
            return myAr;
        }

        public IAsyncResult BeginSendSms(long uid, string message, int sessionID, AsyncCallback callback, object state)
        {
            return BeginSendSms(uid, message, sessionID, false, callback, state);
        }

        public IAsyncResult BeginSendSms(long uid, string message, bool requestSession, AsyncCallback callback, object state)
        {
            return BeginSendSms(uid, message, null, requestSession, callback, state);
        }

        protected virtual void OnSendSmsCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = SendSmsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public void EndSendSms(IAsyncResult ar)
        {
            ar.End<object>();
        }
        #endregion

        #region Can Send SMS
        public void CanSendSmsAsync(long uid, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginCanSendSms(
                uid,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndCanSendSms, OnCanSendSmsCompleted); },
                op);
        }

        public IAsyncResult BeginCanSendSms(long uid, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            var args = new Dictionary<string, string>();
            args.Add("uid", uid.ToString());

            LazyAsyncResult<BoolState> myAr = new LazyAsyncResult<BoolState>(new BoolState("0"), callback, state);
            BeginExecuteApiCall(@"sms.canSend", args, true, false, BoolStateCallback, myAr);
            return myAr;
        }

        protected virtual void OnCanSendSmsCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = CanSendSmsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public bool EndCanSendSms(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }
        #endregion

        #region Send Notification
        [Obsolete(SendNotificationDepricatedMsg)]
        public void SendNotificationAsync(IList<long> recipients, string notification, NotificationTypes notificationType)
        {
            SendNotificationAsync(recipients, notification, notificationType, null);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public void SendNotificationAsync(long recipient, string notification, NotificationTypes notificationType)
        {
            SendNotificationAsync(new List<long> { recipient }, notification, notificationType, null);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public void SendNotificationAsync(IList<long> recipients, string notification, NotificationTypes notificationType, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSendNotification(
                recipients,
                notification,
                notificationType,
                (ar) => { SetupAsyncCompletion<LongListState, IList<long>>(ar, EndSendNotification, OnSendNotificationCompleted); },
                op);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public void SendNotificationAsync(IList<long> recipients, string notification, object userState)
        {
            SendNotificationAsync(recipients, notification, NotificationTypes.General, userState);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public void SendNotificationAsync(long recipient, string notification, object userState)
        {
            SendNotificationAsync(new List<long> { recipient }, notification, userState);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public IAsyncResult BeginSendNotification(IList<long> recipients, string notification, AsyncCallback callback, object state)
        {
            return BeginSendNotification(recipients, notification, NotificationTypes.General, callback, state);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public IAsyncResult BeginSendNotification(IList<long> recipients, string notification, NotificationTypes notificationType, AsyncCallback callback, object state)
        {
            if (APIMode == APIModes.Bebo && notificationType != NotificationTypes.General)
                throw new ArgumentException("Bebo only supports general notifications", "notificationType");

            bool useSession = notificationType == NotificationTypes.General;
            if (useSession) AssertActiveSession();
            if (null == recipients || recipients.Count < 1) throw new ArgumentException("Recipients are required");
            if (string.IsNullOrEmpty(notification)) throw new ArgumentNullException("Notification FBML is required.");

            var args = new Dictionary<string, string>();
            args.Add("to_ids", string.Join(",", recipients.Select(i => i.ToString()).ToArray()));
            args.Add("notification", notification);

            if (APIMode == APIModes.Facebook) //bebo doesn't support this new param
                args.Add("type", notificationType == NotificationTypes.General ? "user_to_user" : "app_to_user");

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall(@"notifications.send", args, useSession, false, LongListCallback, myAr);
            return myAr;
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public IAsyncResult BeginSendNotification(long recipient, string notification, AsyncCallback callback, object state)
        {
            return BeginSendNotification(new List<long> { recipient }, notification, callback, state);
        }

        protected virtual void OnSendNotificationCompleted(object sender, AsyncCompletedEventArgs<IList<long>> e)
        {
            var eh = SendNotificationCompleted;

            if (null != eh)
                eh(sender, e);
        }

        [Obsolete(SendNotificationDepricatedMsg)]
        public IList<long> EndSendNotification(IAsyncResult ar)
        {
            var myAs = ar.End<LongListState>();
            return myAs.Results;
        }
        #endregion

        #region Send Notification Email
        public void SendNotificationEmailAsync(IList<long> recipients, string subject, string text, string fbml, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSendNotificationEmail(
                recipients,
                subject,
                text,
                fbml,
                (ar) => { SetupAsyncCompletion<LongListState, IList<long>>(ar, EndSendNotificationEmail, OnSendNotificationEmailCompleted); },
                op);
        }

        public void SendNotificationEmailAsync(long recipient, string subject, string text, string fbml, object userState)
        {
            SendNotificationEmailAsync(new List<long> { recipient }, subject, text, fbml, userState);
        }

        public IAsyncResult BeginSendNotificationEmail(IList<long> recipients, string subject, string text, string fbml, AsyncCallback callback, object state)
        {
            if (null == recipients || recipients.Count < 1) throw new ArgumentException("Recipients are required");
            if (string.IsNullOrEmpty(subject)) throw new ArgumentNullException("Subject is required");
            if (string.IsNullOrEmpty(text) && string.IsNullOrEmpty(fbml)) throw new ArgumentException("Either text or fbml is required");

            var args = new Dictionary<string, string>();
            args.Add("recipients", recipients.Aggregate<long, string, string>(
                    "", (acc, id) => acc + id.ToString() + ",", r => r.Substring(0, r.Length - 1)));
            args.Add("subject", subject);

            if (!string.IsNullOrEmpty(text))
                args.Add("text", text);

            if (!string.IsNullOrEmpty(fbml))
                args.Add("fbml", fbml);

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall(@"notifications.sendEmail", args, false, false, LongListCallback, myAr);
            return myAr;
        }

        public IAsyncResult BeginSendNotificationEmail(long recipient, string subject, string text, string fbml, AsyncCallback callback, object state)
        {
            return BeginSendNotificationEmail(new List<long> { recipient }, subject, text, fbml, callback, state);
        }

        protected virtual void OnSendNotificationEmailCompleted(object sender, AsyncCompletedEventArgs<IList<long>> e)
        {
            var eh = SendNotificationEmailCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<long> EndSendNotificationEmail(IAsyncResult ar)
        {
            var myAs = ar.End<LongListState>();
            return myAs.Results;
        }
        #endregion

        #region Get Friends
        public void GetFriendsAsync()
        {
            AssertUserId();
            GetFriendsAsync(this.UserId.Value, User.AllFields, User.DefaultOrderBy, null);
        }

        public void GetFriendsAsync(long ofUid)
        {
            GetFriendsAsync(ofUid, User.AllFields, User.DefaultOrderBy, null);
        }

        public void GetFriendsAsync(long ofUid, string orderBy)
        {
            GetFriendsAsync(ofUid, User.AllFields, orderBy, null);
        }

        public void GetFriendsAsync(long ofUid, IEnumerable<string> fieldsToLoad, string orderBy)
        {
            GetFriendsAsync(ofUid, fieldsToLoad, orderBy);
        }

        public void GetFriendsAsync(object userState)
        {
            AssertUserId();
            GetFriendsAsync(this.UserId.Value, User.AllFields, User.DefaultOrderBy, userState);
        }

        public void GetFriendsAsync(long ofUid, object userState)
        {
            GetFriendsAsync(ofUid, User.AllFields, User.DefaultOrderBy, userState);
        }

        public void GetFriendsAsync(long ofUid, string orderBy, object userState)
        {
            GetFriendsAsync(ofUid, User.AllFields, orderBy, userState);
        }

        public void GetFriendsAsync(long ofUid, IEnumerable<string> fieldsToLoad, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetFriends(
                ofUid,
                fieldsToLoad,
                orderBy,
                (ar) => { SetupAsyncCompletion<UserState, IList<User>>(ar, EndGetFriends, OnGetFriendsCompleted); },
                op);
        }

        public IAsyncResult BeginGetFriends(AsyncCallback callback, object state)
        {
            AssertUserId();
            return BeginGetFriends(this.UserId.Value, callback, state);
        }

        public IAsyncResult BeginGetFriends(long ofUid, AsyncCallback callback, object state)
        {
            return BeginGetFriends(ofUid, User.AllFields, User.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetFriends(long ofUid, string orderBy, AsyncCallback callback, object state)
        {
            return BeginGetFriends(ofUid, User.AllFields, orderBy, callback, state);
        }

        public IAsyncResult BeginGetFriends(long ofUid, IEnumerable<string> fieldsToLoad, string orderBy, AsyncCallback callback, object state)
        {
            return BeginGetUsers(
                string.Concat("uid in (select uid2 from friend where uid1=", ofUid.ToString(), ")"),
                fieldsToLoad, orderBy, callback, state);
        }

        protected virtual void OnGetFriendsCompleted(object sender, AsyncCompletedEventArgs<IList<User>> e)
        {
            var eh = GetFriendsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<User> EndGetFriends(IAsyncResult ar)
        {
            return EndGetUsers(ar);
        }

        #endregion

        #region Get FriendIDs
        public void GetFriendIDsAsync()
        {
            AssertUserId();
            GetFriendIDsAsync(this.UserId.Value, null);
        }

        public void GetFriendIDsAsync(long ofUid)
        {
            GetFriendIDsAsync(ofUid, null);
        }

        public void GetFriendIDsAsync(object userState)
        {
            AssertUserId();
            GetFriendIDsAsync(this.UserId.Value, userState);
        }

        public void GetFriendIDsAsync(long ofUid, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetFriendIDs(
                ofUid,
                (ar) => { SetupAsyncCompletion<LongListState, IList<long>>(ar, EndGetFriendIDs, OnGetFriendIDsCompleted); },
                op);
        }

        public IAsyncResult BeginGetFriendIDs(AsyncCallback callback, object state)
        {
            AssertUserId();
            return BeginGetFriendIDs(this.UserId.Value, callback, state);
        }

        public IAsyncResult BeginGetFriendIDs(long ofUid, AsyncCallback callback, object state)
        {
            var myAr = new LazyAsyncResult<LongListState>(callback, state);
            BeginExecuteApiCall("friends.get", new Dictionary<string, string> { { "uid", ofUid.ToString() } }, false, false, GetFriendIDsCallback, myAr);
            return myAr;
        }

        protected virtual void GetFriendIDsCallback(IAsyncResult ar)
        {
            ProcessFqlResult<LongListState>(ar,
                (uids, myAr) =>
                {
                    var resultNodes = uids.DocumentElement.ChildNodes;
                    myAr.MyAsyncState.Results = new List<long>();
                    foreach (XmlNode n in resultNodes)
                    {
                        long uid;
                        if (long.TryParse(n.InnerText, out uid))
                            myAr.MyAsyncState.Results.Add(uid);
                    }
                }
                );
        }

        protected virtual void OnGetFriendIDsCompleted(object sender, AsyncCompletedEventArgs<IList<long>> e)
        {
            var eh = GetFriendIDsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<long> EndGetFriendIDs(IAsyncResult ar)
        {
            var myAs = ar.End<LongListState>();
            return myAs.Results;
        }

        #endregion

        #region Get Users

        public void GetUsersAsync(string where)
        {
            GetUsersAsync(where, User.AllFields, User.DefaultOrderBy, null);
        }

        public void GetUsersAsync(string where, IEnumerable<string> fieldsToLoad)
        {
            GetUsersAsync(where, fieldsToLoad, User.DefaultOrderBy, null);
        }

        public void GetUsersAsync(string where, IEnumerable<string> fieldsToLoad, string orderBy)
        {
            GetUsersAsync(where, fieldsToLoad, orderBy, null);
        }

        public void GetUsersAsync(string where, object userState)
        {
            GetUsersAsync(where, User.AllFields, User.DefaultOrderBy, userState);
        }

        public void GetUsersAsync(string where, IEnumerable<string> fieldsToLoad, object userState)
        {
            GetUsersAsync(where, fieldsToLoad, User.DefaultOrderBy, userState);
        }

        public void GetUsersAsync(string where, IEnumerable<string> fieldsToLoad, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetUsers(
                where,
                fieldsToLoad,
                orderBy,
                (ar) => { SetupAsyncCompletion<UserState, IList<User>>(ar, EndGetUsers, OnGetUsersCompleted); },
                op);
        }

        public IAsyncResult BeginGetUsers(string where, AsyncCallback callback, object state)
        {
            return BeginGetUsers(where, User.AllFields, User.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetUsers(string where, IEnumerable<string> fieldsToLoad, AsyncCallback callback, object state)
        {
            return BeginGetUsers(where, fieldsToLoad, User.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetUsers(string where, IEnumerable<string> fieldsToLoad, string orderBy, AsyncCallback callback, object state)
        {
            return BeginGetUsers(where, fieldsToLoad, true, orderBy, callback, state);
        }

        public IAsyncResult BeginGetUsers(string where, IEnumerable<string> fieldsToLoad, bool useSession, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<UserState> myAr = new LazyAsyncResult<UserState>(callback, state);
            string query = BuildFqlQuery(fieldsToLoad, User.FacebookEntity, where, orderBy);
            BeginExecuteFql(query, false, useSession, GetUsersCallback, myAr);
            return myAr;
        }

        protected virtual void OnGetUsersCompleted(object sender, AsyncCompletedEventArgs<IList<User>> e)
        {
            var eh = GetUsersCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<User> EndGetUsers(IAsyncResult ar)
        {
            UserState myState = ar.End<UserState>();

            return myState.Results;
        }

        protected virtual void GetUsersCallback(IAsyncResult ar)
        {
            ProcessFqlResult<UserState>(ar,
                (users, myAr) => myAr.MyAsyncState.Results = User.FromFqlResult(users, myAr.MyAsyncState.KeepXml));
        }
        #endregion

        #region Get Photos
        public void GetPhotosAsync()
        {
            AssertUserId();
            GetPhotosAsync(Photo.AllFields, "owner=" + this.UserId.Value.ToString(), Photo.DefaultOrderBy, null);
        }

        public void GetPhotosAsync(string where)
        {
            GetPhotosAsync(Photo.AllFields, where, Photo.DefaultOrderBy, null);
        }

        public void GetPhotosAsync(string where, string orderBy)
        {
            GetPhotosAsync(Photo.AllFields, where, orderBy, null);
        }

        public void GetPhotosAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy)
        {
            GetPhotosAsync(fieldsToLoad, where, orderBy, null);
        }

        public void GetPhotosAsync(object userState)
        {
            AssertUserId();
            GetPhotosAsync(Photo.AllFields, "owner=" + this.UserId.Value.ToString(), Photo.DefaultOrderBy, userState);
        }

        public void GetPhotosAsync(string where, object userState)
        {
            GetPhotosAsync(Photo.AllFields, where, Photo.DefaultOrderBy, userState);
        }

        public void GetPhotosAsync(string where, string orderBy, object userState)
        {
            GetPhotosAsync(Photo.AllFields, where, orderBy, userState);
        }

        public void GetPhotosAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetPhotos(
                fieldsToLoad,
                where,
                orderBy,
                (ar) => { SetupAsyncCompletion<PhotoState, IList<Photo>>(ar, EndGetPhotos, OnGetPhotosCompleted); },
                op);
        }

        public IAsyncResult BeginGetPhotos(AsyncCallback callback, object state)
        {
            return BeginGetPhotos(Photo.AllFields, "owner=" + this.UserId.Value.ToString(), Photo.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetPhotos(string where, AsyncCallback callback, object state)
        {
            return BeginGetPhotos(Photo.AllFields, where, Photo.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetPhotos(IEnumerable<string> fieldsToLoad, string where, string orderBy, AsyncCallback callback, object state)
        {
            string query = BuildFqlQuery(fieldsToLoad, Photo.FacebookEntity, where, orderBy);
            LazyAsyncResult<PhotoState> myAr = new LazyAsyncResult<PhotoState>(callback, state);
            BeginExecuteFql(query, false, GetPhotosCallback, myAr);

            return myAr;
        }

        protected virtual void GetPhotosCallback(IAsyncResult ar)
        {
            LazyAsyncResult<PhotoState> myAr = ar.AsyncState as LazyAsyncResult<PhotoState>;
            try
            {
                XmlDocument photos = EndExecuteApiCall(ar);
                myAr.MyAsyncState.Results = Photo.FromFqlResult(photos, myAr.MyAsyncState.KeepXml);

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void OnGetPhotosCompleted(object sender, AsyncCompletedEventArgs<IList<Photo>> e)
        {
            var eh = GetPhotosCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<Photo> EndGetPhotos(IAsyncResult ar)
        {
            PhotoState myState = ar.End<PhotoState>();

            return myState.Results;
        }
        #endregion

        #region Get Group Photos
        public void GetGroupPhotosAsync(long gid)
        {
            GetGroupPhotosAsync(Photo.AllFields, gid, Photo.DefaultOrderBy, null);
        }

        public void GetGroupPhotosAsync(long gid, string orderBy)
        {
            GetGroupPhotosAsync(Photo.AllFields, gid, orderBy, null);
        }

        public void GetGroupPhotosAsync(IEnumerable<string> fieldsToLoad, long gid, string orderBy)
        {
            GetGroupPhotosAsync(fieldsToLoad, gid, orderBy, null);
        }

        public void GetGroupPhotosAsync(long gid, object userState)
        {
            GetGroupPhotosAsync(Photo.AllFields, gid, Photo.DefaultOrderBy, userState);
        }

        public void GetGroupPhotosAsync(long gid, string orderBy, object userState)
        {
            GetGroupPhotosAsync(Photo.AllFields, gid, orderBy, userState);
        }

        public void GetGroupPhotosAsync(IEnumerable<string> fieldsToLoad, long gid, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetGroupPhotos(
                fieldsToLoad,
                gid,
                orderBy,
                (ar) => { SetupAsyncCompletion<PhotoState, IList<Photo>>(ar, EndGetGroupPhotos, OnGetGroupPhotosCompleted); },
                op);
        }

        public IAsyncResult BeginGetGroupPhotos(long gid, AsyncCallback callback, object state)
        {
            return BeginGetGroupPhotos(Photo.AllFields, gid, Photo.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetGroupPhotos(IEnumerable<string> fieldsToLoad, long gid, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<PhotoState> myAr = new LazyAsyncResult<PhotoState>(callback, state);
            Dictionary<string, string> args = new Dictionary<string, string> { { "gid", gid.ToString() } };
            BeginExecuteApiCall("photos.get", args, true, false, GetPhotosCallback, myAr);

            return myAr;
        }

        protected virtual void OnGetGroupPhotosCompleted(object sender, AsyncCompletedEventArgs<IList<Photo>> e)
        {
            var eh = GetGroupPhotosCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<Photo> EndGetGroupPhotos(IAsyncResult ar)
        {
            return EndGetPhotos(ar);
        }
        #endregion

        #region Get Event Photos
        public void GetEventPhotosAsync(long eid)
        {
            GetEventPhotosAsync(Photo.AllFields, eid, Photo.DefaultOrderBy, null);
        }

        public void GetEventPhotosAsync(long eid, string orderBy)
        {
            GetEventPhotosAsync(Photo.AllFields, eid, orderBy, null);
        }

        public void GetEventPhotosAsync(IEnumerable<string> fieldsToLoad, long eid, string orderBy)
        {
            GetEventPhotosAsync(fieldsToLoad, eid, orderBy, null);
        }

        public void GetEventPhotosAsync(long eid, object userState)
        {
            GetEventPhotosAsync(Photo.AllFields, eid, Photo.DefaultOrderBy, userState);
        }

        public void GetEventPhotosAsync(long eid, string orderBy, object userState)
        {
            GetEventPhotosAsync(Photo.AllFields, eid, orderBy, userState);
        }

        public void GetEventPhotosAsync(IEnumerable<string> fieldsToLoad, long eid, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetEventPhotos(
                fieldsToLoad,
                eid,
                orderBy,
                (ar) => { SetupAsyncCompletion<PhotoState, IList<Photo>>(ar, EndGetEventPhotos, OnGetEventPhotosCompleted); },
                op);
        }

        public IAsyncResult BeginGetEventPhotos(long eid, AsyncCallback callback, object state)
        {
            return BeginGetEventPhotos(Photo.AllFields, eid, Photo.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetEventPhotos(IEnumerable<string> fieldsToLoad, long eid, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<PhotoState> myAr = new LazyAsyncResult<PhotoState>(callback, state);
            Dictionary<string, string> args = new Dictionary<string, string> { { "eid", eid.ToString() } };
            BeginExecuteApiCall("photos.get", args, true, false, GetPhotosCallback, myAr);

            return myAr;
        }

        protected virtual void OnGetEventPhotosCompleted(object sender, AsyncCompletedEventArgs<IList<Photo>> e)
        {
            var eh = GetEventPhotosCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<Photo> EndGetEventPhotos(IAsyncResult ar)
        {
            return EndGetPhotos(ar);
        }
        #endregion

        #region Get Session
        public void GetSessionAsync(object userState)
        {
            GetSessionAsync(AuthToken, userState);
        }

        public void GetSessionAsync(string authToken, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetSession(
                authToken,
                (ar) => { SetupAsyncCompletion<WebRequestAsyncState>(ar, EndGetSession, OnGetSessionCompleted); },
                op);
        }

        public IAsyncResult BeginGetSession(string authToken, AsyncCallback callback, object state)
        {
            AuthToken = authToken;

            Dictionary<string, string> parameterList = new Dictionary<string, string>();
            parameterList.Add("method", "auth.getSession");
            parameterList.Add("auth_token", AuthToken);

            return BeginExecuteApiCall(parameterList, false, true, callback, state);
        }

        protected virtual void OnGetSessionCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = GetSessionCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public void EndGetSession(IAsyncResult ar)
        {
            XmlDocument doc = EndExecuteApiCall(ar);

            SessionKey = doc.DocumentElement.GetChildText("session_key");
            UserId = doc.DocumentElement.GetChildLong("uid").Value;
            SessionExpiresUnixTime = doc.DocumentElement.GetChildDouble("expires").Value;

            //if facebook re-wrote our secret we need to record it.
            //this should only happen for desktop apps, though.
            Secret = doc.DocumentElement.GetChildText("secret") ?? Secret;
        }
        #endregion

        #region Get Events
        public void GetEventsAsync()
        {
            AssertUserId();
            GetEventsAsync(Event.AllFields, "eid IN (SELECT eid FROM event_member WHERE uid=" + this.UserId.Value.ToString() + ")", Event.DefaultOrderBy, null);
        }

        public void GetEventsAsync(string where)
        {
            GetEventsAsync(Event.AllFields, where, Event.DefaultOrderBy, null);
        }

        public void GetEventsAsync(string where, string orderBy)
        {
            GetEventsAsync(Event.AllFields, where, orderBy, null);
        }

        public void GetEventsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy)
        {
            GetEventsAsync(fieldsToLoad, where, orderBy, null);
        }

        public void GetEventsAsync(object userState)
        {
            AssertUserId();
            GetEventsAsync(Event.AllFields, "eid IN (SELECT eid FROM event_member WHERE uid=" + this.UserId.Value.ToString() + ")", Event.DefaultOrderBy, userState);
        }

        public void GetEventsAsync(string where, object userState)
        {
            GetEventsAsync(Event.AllFields, where, Event.DefaultOrderBy, userState);
        }

        public void GetEventsAsync(string where, string orderBy, object userState)
        {
            GetEventsAsync(Event.AllFields, where, orderBy, userState);
        }

        public void GetEventsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetEvents(
                fieldsToLoad,
                where,
                orderBy,
                (ar) => { SetupAsyncCompletion<EventState, IList<Event>>(ar, EndGetEvents, OnGetEventsCompleted); },
                op);
        }

        public IAsyncResult BeginGetEvents(AsyncCallback callback, object state)
        {
            AssertUserId();
            return BeginGetEvents(Event.AllFields, "eid IN (SELECT eid FROM event_member WHERE uid=" + this.UserId.Value.ToString() + ")", Event.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetEvents(string where, AsyncCallback callback, object state)
        {
            return BeginGetEvents(Event.AllFields, where, Event.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetEvents(string where, string orderBy, AsyncCallback callback, object state)
        {
            return BeginGetEvents(Event.AllFields, where, orderBy, callback, state);
        }

        public IAsyncResult BeginGetEvents(IEnumerable<string> fieldsToLoad, string where, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<EventState> myAr = new LazyAsyncResult<EventState>(callback, state);
            string query = BuildFqlQuery(fieldsToLoad, Event.FacebookEntity, where, orderBy);
            BeginExecuteFql(query, false, EventsCallback, myAr);
            return myAr;
        }

        protected virtual void EventsCallback(IAsyncResult ar)
        {
            ProcessFqlResult<EventState>(ar,
                (eventXml, myAr) => myAr.MyAsyncState.Results = Event.FromFqlResult(eventXml, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetEventsCompleted(object sender, AsyncCompletedEventArgs<IList<Event>> e)
        {
            var eh = GetEventsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<Event> EndGetEvents(IAsyncResult ar)
        {
            EventState myState = ar.End<EventState>();

            return myState.Results;
        }
        #endregion

        #region Get Groups
        public void GetGroupsAsync()
        {
            AssertUserId();
            GetGroupsAsync(Group.AllFields, "gid IN (SELECT gid FROM group_member WHERE uid=" + this.UserId.Value.ToString() + ")", Group.DefaultOrderBy, null);
        }

        public void GetGroupsAsync(string where)
        {
            GetGroupsAsync(Group.AllFields, where, Group.DefaultOrderBy, null);
        }

        public void GetGroupsAsync(string where, string orderBy)
        {
            GetGroupsAsync(Group.AllFields, where, orderBy, null);
        }

        public void GetGroupsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy)
        {
            GetGroupsAsync(fieldsToLoad, where, orderBy, null);
        }

        public void GetGroupsAsync(object userState)
        {
            AssertUserId();
            GetGroupsAsync(Group.AllFields, "gid IN (SELECT gid FROM group_member WHERE uid=" + this.UserId.Value.ToString() + ")", Group.DefaultOrderBy, userState);
        }

        public void GetGroupsAsync(string where, object userState)
        {
            GetGroupsAsync(Group.AllFields, where, Group.DefaultOrderBy, userState);
        }

        public void GetGroupsAsync(string where, string orderBy, object userState)
        {
            GetGroupsAsync(Group.AllFields, where, orderBy, userState);
        }

        public void GetGroupsAsync(IEnumerable<string> fieldsToLoad, string where, string orderBy, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetGroups(
                fieldsToLoad,
                where,
                orderBy,
                (ar) => { SetupAsyncCompletion<GroupState, IList<Group>>(ar, EndGetGroups, OnGetGroupsCompleted); },
                op);
        }

        public IAsyncResult BeginGetGroups(AsyncCallback callback, object state)
        {
            AssertUserId();
            return BeginGetGroups(Group.AllFields, "gid IN (SELECT gid FROM group_member WHERE uid=" + this.UserId.Value.ToString() + ")", Group.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetGroups(string where, AsyncCallback callback, object state)
        {
            return BeginGetGroups(Group.AllFields, where, Group.DefaultOrderBy, callback, state);
        }

        public IAsyncResult BeginGetGroups(string where, string orderBy, AsyncCallback callback, object state)
        {
            return BeginGetGroups(Group.AllFields, where, orderBy, callback, state);
        }

        public IAsyncResult BeginGetGroups(IEnumerable<string> fieldsToLoad, string where, string orderBy, AsyncCallback callback, object state)
        {
            LazyAsyncResult<GroupState> myAr = new LazyAsyncResult<GroupState>(callback, state);
            string query = BuildFqlQuery(fieldsToLoad, Group.FacebookEntity, where, orderBy);
            BeginExecuteFql(query, false, GroupsCallback, myAr);
            return myAr;
        }

        protected virtual void GroupsCallback(IAsyncResult ar)
        {
            ProcessFqlResult<GroupState>(ar,
                (groupXml, myAr) => myAr.MyAsyncState.Results = Group.FromFqlResult(groupXml, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetGroupsCompleted(object sender, AsyncCompletedEventArgs<IList<Group>> e)
        {
            var eh = GetGroupsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<Group> EndGetGroups(IAsyncResult ar)
        {
            GroupState myState = ar.End<GroupState>();

            return myState.Results;
        }
        #endregion

        #region Publish User Action
        public void PublishUserActionAsync(long templateBundleID, IList<long> targetIDs, string bodyGeneral, IDictionary<string, string> customTokens, IList<ImageToken> images, FlashToken flash, Mp3Token mp3, VideoToken video)
        {
            PublishUserActionAsync(templateBundleID, targetIDs, bodyGeneral, customTokens, images, flash, mp3, video, null);
        }

        public void PublishUserActionAsync(long templateBundleID, IList<long> targetIDs, string bodyGeneral, IDictionary<string, string> customTokens, IList<ImageToken> images, FlashToken flash, Mp3Token mp3, VideoToken video, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginPublishUserAction(
                templateBundleID,
                targetIDs,
                bodyGeneral,
                customTokens,
                images,
                flash,
                mp3,
                video,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndPublishUserAction, OnPublishUserActionCompleted); },
                op);
        }

        protected virtual void OnPublishUserActionCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = PublishUserActionCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IAsyncResult BeginPublishUserAction(long templateBundleID, IList<long> targetIDs, string bodyGeneral, IDictionary<string, string> customTokens, IList<ImageToken> images, FlashToken flash, Mp3Token mp3, VideoToken video, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            LazyAsyncResult<BoolState> myAr = new LazyAsyncResult<BoolState>(new BoolState("1"), callback, state);
            var args = new Dictionary<string, string>();
            args.Add("template_bundle_id", templateBundleID.ToString());

            var templateData = new Dictionary<string, object>();
            if (null != customTokens && customTokens.Count > 0)
                foreach (var kvp in customTokens)
                    templateData.Add(kvp.Key, kvp.Value);

            if (null != images && images.Count > 0) templateData.Add("images", images);
            if (null != flash) templateData.Add("flash", flash);
            if (null != mp3) templateData.Add("mp3", mp3);
            if (null != video) templateData.Add("video", video);

            if (templateData.Count > 0) args.Add("template_data", new JavaScriptSerializer().Serialize(templateData));

            if (null != targetIDs && targetIDs.Count > 0) args.Add("target_ids", string.Join(",", targetIDs.Select(id => id.ToString()).ToArray()));
            if (!string.IsNullOrEmpty(bodyGeneral)) args.Add("body_general", bodyGeneral);

            BeginExecuteApiCall("feed.publishUserAction", args, true, false, BoolStateCallback, myAr);
            return myAr;
        }

        public bool EndPublishUserAction(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }

        #endregion

        #region Association

        public void DefineAssociationAsync(string name, AssociationTypes type, IDictionary<string, string> assoc_info1, IDictionary<string, string> assoc_info2, string inverse)
        {
            DefineAssociationAsync(name, type, assoc_info1, assoc_info2, inverse, null);
        }

        public void DefineAssociationAsync(string name, AssociationTypes type, IDictionary<string, string> assoc_info1, IDictionary<string, string> assoc_info2, string inverse, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginDefineAssociation(
                name,
                type,
                assoc_info1,
                assoc_info2,
                inverse,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndDefineAssociation, OnDefineAssociationCompleted); },
                op);
        }

        public IAsyncResult BeginDefineAssociation(string name, AssociationTypes type, IDictionary<string, string> assoc_info1, IDictionary<string, string> assoc_info2, string inverse, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            LazyAsyncResult<BoolState> myAr = new LazyAsyncResult<BoolState>(new BoolState("1"), callback, state);
            var args = new Dictionary<string, string>();

            args.Add("name", name);
            args.Add("assoc_type", ((int)type).ToString());

            args.Add("assoc_info1", assoc_info1.ToJsonAssociativeArray());
            args.Add("assoc_info2", assoc_info2.ToJsonAssociativeArray());

            if (!String.IsNullOrEmpty(inverse)) args.Add("inverse", inverse);

            BeginExecuteApiCall("data.defineAssociation", args, true, false, BoolStateCallback, myAr);

            return myAr;
        }

        public bool EndDefineAssociation(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }

        protected virtual void OnDefineAssociationCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = DefineAssociationCompleted;

            if (null != eh)
            {
                eh(sender, e);
            }

        }

        #endregion

        #region ObjectType

        public void CreateObjectTypeAsync(string name)
        {
            CreateObjectTypeAsync(name, null);
        }

        public void CreateObjectTypeAsync(string name, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginCreateObjectType(
                name,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndCreateObjectType, OnCreateObjectTypeCompleted); },
                op);
        }

        public IAsyncResult BeginCreateObjectType(string name, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            LazyAsyncResult<BoolState> myAr = new LazyAsyncResult<BoolState>(new BoolState("1"), callback, state);
            var args = new Dictionary<string, string>();

            args.Add("name", name);
            args.Add("uid", UserId.ToString());

            BeginExecuteApiCall("data.createObjectType", args, false, false, BoolStateCallback, myAr);

            return myAr;
        }

        public bool EndCreateObjectType(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }

        protected virtual void OnCreateObjectTypeCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = CreateObjectTypeCompleted;

            if (null != eh)
            {
                eh(sender, e);
            }

        }

        #endregion

        #region Register Template Bundle
        public void RegisterTemplateBundleAsync(StoryTemplateBundle templateBundle)
        {
            RegisterTemplateBundleAsync(templateBundle, null);
        }

        public void RegisterTemplateBundleAsync(StoryTemplateBundle templateBundle, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginRegisterTemplateBundle(
                templateBundle,
                (ar) => { SetupAsyncCompletion<LongListState, long>(ar, EndRegisterTemplateBundle, OnRegisterTemplateBundleCompleted); },
                op);
        }

        protected virtual void OnRegisterTemplateBundleCompleted(object sender, AsyncCompletedEventArgs<long> e)
        {
            var eh = RegisterTemplateBundleCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IAsyncResult BeginRegisterTemplateBundle(StoryTemplateBundle templateBundle, AsyncCallback callback, object state)
        {
            if (null == templateBundle) throw new ArgumentNullException("templateBundle");

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            var args = new Dictionary<string, string>();
            args.Add("one_line_story_templates", new JavaScriptSerializer().Serialize(templateBundle.OneLineStories.Select(t => t.template_title).ToArray()));

            if (null != templateBundle.ShortStories && templateBundle.ShortStories.Count > 0)
                args.Add("short_story_templates", new JavaScriptSerializer().Serialize(templateBundle.ShortStories));

            if (null != templateBundle.FullStory)
                args.Add("full_story_template", new JavaScriptSerializer().Serialize(templateBundle.FullStory));

            if (null != templateBundle.ActionLinks && templateBundle.ActionLinks.Count > 0)
                args.Add("action_links", new JavaScriptSerializer().Serialize(templateBundle.ActionLinks));

            BeginExecuteApiCall("feed.registerTemplateBundle", args, false, false, LongListCallback, myAr);
            return myAr;
        }

        public long EndRegisterTemplateBundle(IAsyncResult ar)
        {
            LongListState myState = ar.End<LongListState>();
            if (myState.Results.Count == 0) throw new FacebookException("Error registering template bundle");
            return myState.Results[0];
        }

        #endregion

        #region Admin Get Allocations
        public void AdminGetAllocationAsync(AllocationType allocationType)
        {
            AdminGetAllocationAsync(allocationType, null);
        }

        public void AdminGetAllocationAsync(string allocationType)
        {
            AdminGetAllocationAsync(allocationType, null);
        }

        public void AdminGetAllocationAsync(AllocationType allocationType, object userState)
        {
            AdminGetAllocationAsync(allocationType.ToString(), userState);
        }

        public void AdminGetAllocationAsync(string allocationType, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginAdminGetAllocation(
                allocationType,
                (ar) => { SetupAsyncCompletion<LongListState, long>(ar, EndAdminGetAllocation, OnAdminGetAllocationCompleted); },
                op);
        }

        protected virtual void OnAdminGetAllocationCompleted(object sender, AsyncCompletedEventArgs<long> e)
        {
            var eh = AdminGetAllocationCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IAsyncResult BeginAdminGetAllocation(AllocationType allocationType, AsyncCallback callback, object state)
        {
            return BeginAdminGetAllocation(allocationType.ToString(), callback, state);
        }

        public IAsyncResult BeginAdminGetAllocation(string allocationType, AsyncCallback callback, object state)
        {
            if (string.IsNullOrEmpty(allocationType)) throw new ArgumentNullException("allocationType");

            LazyAsyncResult<LongListState> myAr = new LazyAsyncResult<LongListState>(callback, state);
            var args = new Dictionary<string, string>();
            args.Add("integration_point_name", allocationType);

            BeginExecuteApiCall("admin.getAllocation", args, false, false, LongListCallback, myAr);
            return myAr;
        }

        public long EndAdminGetAllocation(IAsyncResult ar)
        {
            LongListState myState = ar.End<LongListState>();
            if (myState.Results.Count == 0) throw new FacebookException("Error getting admin allocation");
            return myState.Results[0];
        }

        #endregion

        #region Deactivate Template Bundle
        public void DeactivateTemplateBundleAsync(long id)
        {
            DeactivateTemplateBundleAsync(id, null);
        }

        public void DeactivateTemplateBundleAsync(long id, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginDeactivateTemplateBundle(
                id,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndDeactivateTemplateBundle, OnDeactivateTemplateBundleCompleted); },
                op);
        }

        protected virtual void OnDeactivateTemplateBundleCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = DeactivateTemplateBundleCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IAsyncResult BeginDeactivateTemplateBundle(long id, AsyncCallback callback, object state)
        {
            LazyAsyncResult<BoolState> myAr = new LazyAsyncResult<BoolState>(new BoolState("1"), callback, state);
            var args = new Dictionary<string, string>();
            args.Add("template_bundle_id", id.ToString());
            BeginExecuteApiCall("feed.deactivateTemplateBundleByID", args, false, false, BoolStateCallback, myAr);
            return myAr;
        }

        public bool EndDeactivateTemplateBundle(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }

        #endregion

        #region Get Template Bundles
        public void GetRegisteredTemplateBundlesAsync()
        {
            GetRegisteredTemplateBundlesAsync(null);
        }

        public void GetRegisteredTemplateBundlesAsync(object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetRegisteredTemplateBundles(
                (ar) => { SetupAsyncCompletion<StoryTemplateBundleState, IList<StoryTemplateBundle>>(ar, EndGetRegisteredTemplateBundles, OnGetRegisteredTemplateBundlesCompleted); },
                op);
        }

        public IAsyncResult BeginGetRegisteredTemplateBundles(AsyncCallback callback, object state)
        {
            LazyAsyncResult<StoryTemplateBundleState> myAr = new LazyAsyncResult<StoryTemplateBundleState>(callback, state);
            var args = new Dictionary<string, string>();
            BeginExecuteApiCall("feed.getRegisteredTemplateBundles", args, false, false, GetRegisteredTemplateBundlesCallback, myAr);
            return myAr;
        }

        protected virtual void GetRegisteredTemplateBundlesCallback(IAsyncResult ar)
        {
            ProcessFqlResult<StoryTemplateBundleState>(ar,
                (templates, myAr) => myAr.MyAsyncState.Results = StoryTemplateBundle.FromFqlResult(templates, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetRegisteredTemplateBundlesCompleted(object sender, AsyncCompletedEventArgs<IList<StoryTemplateBundle>> e)
        {
            var eh = GetRegisteredTemplateBundlesCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<StoryTemplateBundle> EndGetRegisteredTemplateBundles(IAsyncResult ar)
        {
            return ar.End<StoryTemplateBundleState>().Results;
        }

        #endregion

        #region Get Template Bundle By ID
        public void GetRegisteredTemplateBundleAsync(long id)
        {
            GetRegisteredTemplateBundleAsync(id, null);
        }

        public void GetRegisteredTemplateBundleAsync(long id, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetRegisteredTemplateBundle(
                id,
                (ar) => { SetupAsyncCompletion<StoryTemplateBundleState, StoryTemplateBundle>(ar, EndGetRegisteredTemplateBundle, OnGetRegisteredTemplateBundleCompleted); },
                op);
        }

        public IAsyncResult BeginGetRegisteredTemplateBundle(long id, AsyncCallback callback, object state)
        {
            LazyAsyncResult<StoryTemplateBundleState> myAr = new LazyAsyncResult<StoryTemplateBundleState>(callback, state);
            var args = new Dictionary<string, string>();
            args.Add("template_bundle_id", id.ToString());
            BeginExecuteApiCall("feed.getRegisteredTemplateBundleByID", args, false, false, GetRegisteredTemplateBundleCallback, myAr);
            return myAr;
        }

        protected virtual void GetRegisteredTemplateBundleCallback(IAsyncResult ar)
        {
            ProcessFqlResult<StoryTemplateBundleState>(ar,
                (template, myAr) => myAr.MyAsyncState.Results = new List<StoryTemplateBundle> { StoryTemplateBundle.FromSingleResult(template, myAr.MyAsyncState.KeepXml) });
        }

        protected virtual void OnGetRegisteredTemplateBundleCompleted(object sender, AsyncCompletedEventArgs<StoryTemplateBundle> e)
        {
            var eh = GetRegisteredTemplateBundleCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public StoryTemplateBundle EndGetRegisteredTemplateBundle(IAsyncResult ar)
        {
            return ar.End<StoryTemplateBundleState>().Results.SingleOrDefault();
        }
        #endregion

        #region Set Profile Info Options
        public void SetProfileInfoOptionsAsync(string field, IList<InfoField> options)
        {
            SetProfileInfoOptionsAsync(field, options, null);
        }

        public void SetProfileInfoOptionsAsync(string field, IList<InfoField> options, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSetProfileInfoOptions(
                field,
                options,
                (ar) => { SetupAsyncCompletion<object>(ar, EndSetProfileInfoOptions, OnSetProfileInfoOptionsCompleted); },
                op);
        }

        public IAsyncResult BeginSetProfileInfoOptions(string field, IList<InfoField> options, AsyncCallback callback, object state)
        {
            if (string.IsNullOrEmpty(field)) throw new ArgumentNullException("field");
            if (null == options || options.Count == 0) throw new ArgumentException("options are required", "options");

            LazyAsyncResult<object> myAr = new LazyAsyncResult<object>(callback, state);

            var args = new Dictionary<string, string>();
            args.Add("field", field);
            args.Add("options", new JavaScriptSerializer().Serialize(options));

            BeginExecuteApiCall("profile.setInfoOptions", args, false, false, ObjectStateCallback, myAr);

            return myAr;
        }

        protected virtual void OnSetProfileInfoOptionsCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = SetProfileInfoOptionsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public void EndSetProfileInfoOptions(IAsyncResult ar)
        {
            ar.End<object>();
        }
        #endregion

        #region Set Profile Info
        public void SetProfileInfoAsync(long uid, InfoSection section)
        {
            SetProfileInfoAsync(uid, section, null);
        }

        public void SetProfileInfoAsync(long uid, InfoSection section, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSetProfileInfo(
                uid,
                section,
                (ar) => { SetupAsyncCompletion<object>(ar, EndSetProfileInfo, OnSetProfileInfoCompleted); },
                op);
        }

        public IAsyncResult BeginSetProfileInfo(long uid, InfoSection section, AsyncCallback callback, object state)
        {
            if (null == section) throw new ArgumentNullException("section");
            if (null == section.info_fields) throw new ArgumentNullException("section.info_fields");
            if (string.IsNullOrEmpty(section.title)) throw new ArgumentNullException("section.title");
            if (section.info_fields.Count == 0) throw new ArgumentException("section.info_fields must contain fields", "section.info_fields");

            LazyAsyncResult<object> myAr = new LazyAsyncResult<object>(callback, state);

            var args = new Dictionary<string, string>();
            args.Add("uid", uid.ToString());
            args.Add("info_fields", new JavaScriptSerializer().Serialize(section.info_fields));
            args.Add("type", ((int)section.type).ToString());
            args.Add("title", section.title);

            BeginExecuteApiCall("profile.setInfo", args, false, false, ObjectStateCallback, myAr);

            return myAr;
        }

        protected virtual void OnSetProfileInfoCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var eh = SetProfileInfoCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public void EndSetProfileInfo(IAsyncResult ar)
        {
            ar.End<object>();
        }
        #endregion

        #region Get Profile Info Options
        public void GetProfileInfoOptionsAsync(string field)
        {
            GetProfileInfoOptionsAsync(field, null);
        }

        public void GetProfileInfoOptionsAsync(string field, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetProfileInfoOptions(
                field,
                (ar) => { SetupAsyncCompletion<InfoOptionsAsyncState, IList<InfoItem>>(ar, EndGetProfileInfoOptions, OnGetProfileInfoOptionsCompleted); },
                op);
        }

        public IAsyncResult BeginGetProfileInfoOptions(string field, AsyncCallback callback, object state)
        {
            LazyAsyncResult<StoryTemplateBundleState> myAr = new LazyAsyncResult<StoryTemplateBundleState>(callback, state);
            var args = new Dictionary<string, string>();
            args.Add("field", field);
            BeginExecuteApiCall("profile.getInfoOptions", args, false, false, GetProfileInfoOptionsCallback, myAr);
            return myAr;
        }

        protected virtual void GetProfileInfoOptionsCallback(IAsyncResult ar)
        {
            ProcessFqlResult<InfoOptionsAsyncState>(ar,
                (results, myAr) => myAr.MyAsyncState.Results = InfoItem.FromFqlResult(results, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetProfileInfoOptionsCompleted(object sender, AsyncCompletedEventArgs<IList<InfoItem>> e)
        {
            var eh = GetProfileInfoOptionsCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public IList<InfoItem> EndGetProfileInfoOptions(IAsyncResult ar)
        {
            return ar.End<InfoOptionsAsyncState>().Results;
        }

        #endregion

        #region Get Profile Info
        public void GetProfileInfoSectionAsync(long uid)
        {
            GetProfileInfoSectionAsync(uid, null);
        }

        public void GetProfileInfoSectionAsync(long uid, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginGetProfileInfoSection(
                uid,
                (ar) => { SetupAsyncCompletion<InfoSectionAsyncState, InfoSection>(ar, EndGetProfileInfoSection, OnGetProfileInfoSectionCompleted); },
                op);
        }

        public IAsyncResult BeginGetProfileInfoSection(long uid, AsyncCallback callback, object state)
        {
            LazyAsyncResult<StoryTemplateBundleState> myAr = new LazyAsyncResult<StoryTemplateBundleState>(callback, state);
            var args = new Dictionary<string, string>();
            args.Add("uid", uid.ToString());
            BeginExecuteApiCall("profile.getInfo", args, false, false, GetProfileInfoSectionCallback, myAr);
            return myAr;
        }

        protected virtual void GetProfileInfoSectionCallback(IAsyncResult ar)
        {
            ProcessFqlResult<InfoSectionAsyncState>(ar,
                (results, myAr) => myAr.MyAsyncState.Results = InfoSection.FromFqlResult(results, myAr.MyAsyncState.KeepXml));
        }

        protected virtual void OnGetProfileInfoSectionCompleted(object sender, AsyncCompletedEventArgs<InfoSection> e)
        {
            var eh = GetProfileInfoSectionCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public InfoSection EndGetProfileInfoSection(IAsyncResult ar)
        {
            return ar.End<InfoSectionAsyncState>().Results;
        }

        #endregion

        #region SetUserPreference

        public void SetUserPreferenceAsync(short prefId, string value)
        {
            SetUserPreferenceAsync(prefId, value, null);
        }

        public void SetUserPreferenceAsync(long prefId, string value, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginSetUserPreference(
                prefId,
                value,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndSetUserPreference, OnSetUserPreferenceCompleted); },
                op);
        }

        protected virtual void OnSetUserPreferenceCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = SetUserPreferenceCompleted;

            if (null != eh)
            {
                eh(sender, e);
            }

        }

        public IAsyncResult BeginSetUserPreference(long prefId, string value, AsyncCallback callback, object state)
        {
            AssertActiveSession();

            LazyAsyncResult<BoolState> myAr = new LazyAsyncResult<BoolState>(new BoolState("1"), callback, state);
            var args = new Dictionary<string, string>();
            args.Add("pref_id", prefId.ToString());
            args.Add("value", value);

            BeginExecuteApiCall("data.setUserPreference", args, false, false, BoolStateCallback, myAr);

            return myAr;
        }

        public bool EndSetUserPreference(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }

        #endregion

        #region Execute Fql
        public void ExecuteFqlAsync(string fql, bool secure, object userState)
        {
            ExecuteFqlAsync(fql, secure, true, userState);
        }

        public void ExecuteFqlAsync(string fql, bool secure, bool useSession, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginExecuteFql(
                fql,
                secure,
                useSession,
                (ar) => { SetupAsyncCompletion<WebRequestAsyncState, XmlDocument>(ar, EndExecuteFql, OnExecuteFqlCompleted); },
                op);
        }

        public IAsyncResult BeginExecuteFql(string fql, bool secure, AsyncCallback callback, object state)
        {
            return BeginExecuteFql(fql, secure, true, callback, state);
        }

        public IAsyncResult BeginExecuteFql(string fql, bool secure, bool useSession, AsyncCallback callback, object state)
        {
            if (useSession) AssertActiveSession();

            Dictionary<string, string> parameterList = new Dictionary<string, string>();
            parameterList.Add("method", "fql.query");
            parameterList.Add("query", fql);

            return BeginExecuteApiCall(parameterList, useSession, secure, callback, state);
        }

        protected virtual void ProcessFqlResult<TAsyncState>(IAsyncResult ar, Action<XmlDocument, LazyAsyncResult<TAsyncState>> processResult)
            where TAsyncState : class, new()
        {
            LazyAsyncResult<TAsyncState> myAr = ar.AsyncState as LazyAsyncResult<TAsyncState>;
            try
            {
                XmlDocument results = EndExecuteFql(ar);
                processResult(results, myAr);

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void OnExecuteFqlCompleted(object sender, AsyncCompletedEventArgs<XmlDocument> e)
        {
            var eh = ExecuteFqlCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public XmlDocument EndExecuteFql(IAsyncResult ar)
        {
            return EndExecuteApiCall(ar);
        }
        #endregion

        #region Execute Api Call

        private void LongListCallback(IAsyncResult ar)
        {
            LazyAsyncResult<LongListState> myAr = ar.AsyncState as LazyAsyncResult<LongListState>;
            try
            {
                XmlDocument result = EndExecuteApiCall(ar);
                string csvList = result.DocumentElement.InnerText;
                if (string.IsNullOrEmpty(csvList))
                    myAr.MyAsyncState.Results = new List<long>();
                else
                    myAr.MyAsyncState.Results = new List<long>(
                        csvList.Split(new char[] { ',' }).Select(i => long.Parse(i)));

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        private void LongListCallbackWithUnderscores(IAsyncResult ar)
        {
            LazyAsyncResult<LongListState> myAr = ar.AsyncState as LazyAsyncResult<LongListState>;
            try
            {
                XmlDocument result = EndExecuteApiCall(ar);
                string csvList = result.DocumentElement.InnerText;
                if (string.IsNullOrEmpty(csvList))
                    myAr.MyAsyncState.Results = new List<long>();
                else
                    myAr.MyAsyncState.Results = new List<long>(
                        csvList.Split(new char[] { ',', '_' }).Select(i => long.Parse(i)));

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        public void ExecuteApiCallAsync(string method, IDictionary<string, string> parameterDictionary, bool useSession, bool secure, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginExecuteApiCall(
                method,
                parameterDictionary,
                useSession,
                secure,
                (ar) => { SetupAsyncCompletion<WebRequestAsyncState, XmlDocument>(ar, EndExecuteApiCall, OnExecuteApiCallCompleted); },
                op);
        }


        public IAsyncResult BeginExecuteApiCall(string method, IDictionary<string, string> parameterDictionary, bool useSession, bool secure, AsyncCallback callback, object state)
        {
            if (!parameterDictionary.ContainsKey("method"))
                parameterDictionary.Add("method", method);

            return BeginExecuteApiCall(parameterDictionary, useSession, secure, callback, state);
        }

        protected IAsyncResult BeginExecuteApiCall(IDictionary<string, string> parameterDictionary, bool useSession, bool secure, AsyncCallback callback, object state)
        {
            //use the default instance session key if not overriden by the caller
            if (useSession && !parameterDictionary.ContainsKey("session_key"))
                parameterDictionary.Add("session_key", SessionKey);

            string requestUrl = GetRequestUrl(secure);
            string encodedParams = GetEncodedParameters(parameterDictionary);

            return BeginGetQueryResponse(requestUrl, encodedParams, callback, state);
        }

        protected Stream EndExecuteApiCallStream(IAsyncResult ar)
        {
            return EndGetQueryResponseStream(ar);
        }

        protected virtual void OnExecuteApiCallCompleted(object sender, AsyncCompletedEventArgs<XmlDocument> e)
        {
            var eh = ExecuteApiCallCompleted;

            if (null != eh)
                eh(sender, e);
        }


        public XmlDocument EndExecuteApiCall(IAsyncResult ar)
        {
            return EndGetQueryResponse(ar);
        }
        #endregion

        #region Web Request
        protected IAsyncResult BeginGetQueryResponse(string requestUrl, string postData, AsyncCallback callback, object state)
        {
            //TODO: figure out how to do it on a per connection basis. apparently api.new.facebook.com has an invalid CN in the cert.
            ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { System.Diagnostics.Debug.WriteLine(sslPolicyErrors.ToString()); return true; };

            // Create a web request for input path.
            WebRequest webRequest = WebRequest.Create(requestUrl);
            webRequest.Method = "POST";
            webRequest.ContentType = "application/x-www-form-urlencoded";
            webRequest.Proxy = null;
            webRequest.UseDefaultCredentials = true;
            webRequest.Timeout = 15000; //TODO: configuration option

            WebRequestAsyncState myState = new WebRequestAsyncState();
            myState.Request = webRequest;
            LazyAsyncResult<WebRequestAsyncState> myAr = new LazyAsyncResult<WebRequestAsyncState>(myState, callback, state);

            if (string.IsNullOrEmpty(postData))
            {
                webRequest.BeginGetResponse(WebResponseCallback, myAr);
            }
            else
            {
                myAr.MyAsyncState.RequestData = Encoding.UTF8.GetBytes(postData);
                webRequest.ContentLength = myAr.MyAsyncState.RequestData.Length;
                webRequest.BeginGetRequestStream(WebRequestCallback, myAr);
            }

            return myAr;
        }

        protected virtual void WebRequestCallback(IAsyncResult ar)
        {
            LazyAsyncResult<WebRequestAsyncState> myAr = ar.AsyncState as LazyAsyncResult<WebRequestAsyncState>;
            try
            {
                myAr.MyAsyncState.RequestStream = myAr.MyAsyncState.Request.EndGetRequestStream(ar);
                myAr.MyAsyncState.RequestStream.BeginWrite(myAr.MyAsyncState.RequestData, 0, myAr.MyAsyncState.RequestData.Length, WebRequestCompleteCallback, myAr);
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void WebRequestCompleteCallback(IAsyncResult ar)
        {
            LazyAsyncResult<WebRequestAsyncState> myAr = ar.AsyncState as LazyAsyncResult<WebRequestAsyncState>;
            try
            {
                myAr.MyAsyncState.RequestStream.EndWrite(ar);
                myAr.MyAsyncState.Request.BeginGetResponse(WebResponseCallback, myAr);
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void WebResponseCallback(IAsyncResult ar)
        {
            LazyAsyncResult<WebRequestAsyncState> myAr = ar.AsyncState as LazyAsyncResult<WebRequestAsyncState>;
            try
            {
                WebResponse resp = myAr.MyAsyncState.Request.EndGetResponse(ar);

                //if we get bigger than 2gb responses, we're in trouble. :)
                //we often (always?) get a "-1" resonse length back from Facebook. bizarre.
                int initialStreamCapacity = resp.ContentLength > 0 ? (int)resp.ContentLength : 65535;
                myAr.MyAsyncState.Result = new MemoryStream(initialStreamCapacity);
                myAr.MyAsyncState.ResponseStream = resp.GetResponseStream();

                byte[] buff = new byte[65535]; //TODO: buffer pool for less memory thrash
                myAr.MyAsyncState.ResponseBuffer = buff;

                myAr.MyAsyncState.ResponseStream.BeginRead(buff, 0, buff.Length, WebResponseBufferCallback, myAr);
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void WebResponseBufferCallback(IAsyncResult ar)
        {
            LazyAsyncResult<WebRequestAsyncState> myAr = ar.AsyncState as LazyAsyncResult<WebRequestAsyncState>;
            Stream respStream = myAr.MyAsyncState.ResponseStream;
            Stream resultStream = myAr.MyAsyncState.Result;
            byte[] buff = myAr.MyAsyncState.ResponseBuffer;

            try
            {
                int readLen = respStream.EndRead(ar);

                if (readLen == 0) // EOF
                {
                    //get rid of that extra 64KB ASAP
                    myAr.MyAsyncState.ResponseBuffer = null;

                    //don't need the web request stream either
                    respStream.Dispose();

                    resultStream.Seek(0, SeekOrigin.Begin);

                    if (!myAr.IsCompleted)
                        myAr.SetComplete();
                }
                else
                {
                    //TODO: Async this write. For MemoryStream it doesn't matter, but for any I/O based one it would.
                    resultStream.Write(buff, 0, readLen);
                    respStream.BeginRead(buff, 0, buff.Length, WebResponseBufferCallback, myAr);
                }
            }
            catch (Exception ex)
            {
                if (null != respStream)
                    respStream.Dispose();

                if (null != resultStream)
                    resultStream.Dispose();

                if (null != buff)
                    myAr.MyAsyncState.ResponseBuffer = null;

                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected Stream EndGetQueryResponseStream(IAsyncResult ar)
        {
            var myAr = ar as LazyAsyncResult<WebRequestAsyncState>;

            return myAr.End<WebRequestAsyncState>().Result;
        }

        protected XmlDocument EndGetQueryResponse(IAsyncResult ar)
        {
            XmlDocument doc = new XmlDocument();
            using (Stream s = EndGetQueryResponseStream(ar))
            {
                doc.Load(s);
            }

            ErrorCheck(doc);

            return doc;
        }
        #endregion

        #region Utility
        public bool IsSessionActive()
        {
            return !string.IsNullOrEmpty(SessionKey);
        }

        public FacebookConnection Clone(bool includeSecret, bool includeAuthToken)
        {
            var result = new FacebookConnection
            {
                ApplicationKey = null != this.ApplicationKey ? string.Copy(this.ApplicationKey) : null,
                AuthToken = (includeAuthToken && null != this.AuthToken) ? string.Copy(this.AuthToken) : null,
                Secret = (includeSecret && null != this.Secret) ? string.Copy(this.Secret) : null,
                SessionExpiresUnixTime = this.SessionExpiresUnixTime,
                SessionKey = null != this.SessionKey ? string.Copy(this.SessionKey) : null,
                UserId = null != this.UserId ? new long?(this.UserId.Value) : null,
            };

            return result;
        }

        public string Serialize()
        {
            return Serialize(true, true);
        }

        public string Serialize(bool includeSecret, bool includeAuthToken)
        {
            //i don't like doing double serialization here, 
            //but haven't taken time to figure out how to pass the StreamingContext through the DataContractJsonSerializer like with BinaryFormatter.
            FacebookConnection miniMe = Clone(includeSecret, includeAuthToken);
            using (MemoryStream ms = new MemoryStream())
            {
                new DataContractJsonSerializer(this.GetType()).WriteObject(ms, miniMe);
                return Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
            }
        }

        public static FacebookConnection Deserialize(string serialized)
        {
            return Deserialize(serialized, typeof(FacebookConnection));
        }

        public static FacebookConnection Deserialize(string serialized, Type cnnType)
        {
            byte[] buff = Encoding.UTF8.GetBytes(serialized);
            using (MemoryStream ms = new MemoryStream(buff))
                return new DataContractJsonSerializer(cnnType).ReadObject(ms) as FacebookConnection;
        }

        #region ISerializable Members

        [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public FacebookConnection(SerializationInfo info, StreamingContext context)
            : this()
        {
            ApplicationKey = info.GetString("ak");
            Secret = info.GetString("s");
            SessionKey = info.GetString("sk");
            AuthToken = info.GetString("at");
            UserId = info.GetString("u") == null ? (long?)null : (long?)Int64.Parse(info.GetString("u")); //going through string isn't the most optimal but was having issues with int32 to long? cast
            SessionExpiresUnixTime = info.GetDouble("se");
        }

        [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            bool includeSecret = true, includeAuthToken = true;
            bool[] parameters = context.Context as bool[];
            if (null != parameters)
            {
                includeSecret = parameters[0];
                includeAuthToken = parameters[1];
            }

            info.AddValue("v", (byte)1);
            info.AddValue("ak", ApplicationKey);
            info.AddValue("s", includeSecret ? Secret : (string)null);
            info.AddValue("sk", SessionKey);
            info.AddValue("at", includeAuthToken ? AuthToken : (string)null);
            info.AddValue("u", UserId);
            info.AddValue("se", SessionExpiresUnixTime);
        }

        #endregion

        protected void AssertActiveSession()
        {
            if (!IsSessionActive())
                throw new InvalidOperationException("An active session is required.");
        }

        protected void AssertUserId()
        {
            if (!UserId.HasValue)
                throw new InvalidOperationException("A UserId is required.");
        }

        protected virtual void ErrorCheck(XmlDocument doc)
        {
            XmlElement errRoot = doc["error_response", FacebookNamespace];
            if (null != errRoot)
            {
                var err = new Schema.ErrorResponse {
                    Error_Code = errRoot.GetChildInt("error_code").Value,
                    Error_Msg = errRoot.GetChildText("error_msg"),
                };

                ErrorCheck(err);
            }
        }

        protected virtual void ErrorCheck(Schema.ErrorResponse err)
        {
            // Custom exception for some of the errors
            switch (err.Error_Code)
            {
                case 1:
                    throw new FacebookUnknownException(err.Error_Msg);
                case 2:
                    throw new FacebookServiceUnavailableException(err.Error_Msg);
                case 4:
                    throw new FacebookRequestLimitException(err.Error_Msg);
                case 102:
                    throw new FacebookTimeoutException(err.Error_Msg);
                case 104:
                    throw new FacebookSigningException(err.Error_Msg);
                case 110:
                    throw new FacebookInvalidUserException(err.Error_Msg);
                case 120:
                    throw new FacebookInvalidAlbumException(err.Error_Msg);
                case 200:
                    throw new FacebookPermissionException(err.Error_Msg);
                case 210: // user not visible
                case 220: // album not visible
                case 221: // photo not visible
                    throw new FacebookNotVisibleException(err.Error_Msg);
                case 601:
                    throw new FacebookInvalidFqlSyntaxException(err.Error_Msg);
                default:
                    throw new FacebookException(err.Error_Msg);
            }
        }

        protected string GetEncodedParameters(IDictionary<string, string> parameterDictionary)
        {
            StringBuilder queryBuilder = new StringBuilder();

            parameterDictionary.Add("api_key", ApplicationKey);
            parameterDictionary.Add("v", Version);
            parameterDictionary.Add("call_id", DateTime.UtcNow.Ticks.ToString());
            parameterDictionary.Add("sig", GenerateSignature(parameterDictionary));


            // Build the query
            foreach (KeyValuePair<string, string> kvp in parameterDictionary)
            {
                queryBuilder.Append(kvp.Key);
                queryBuilder.Append("=");
                queryBuilder.Append(HttpUtility.UrlEncode(kvp.Value));
                queryBuilder.Append("&");
            }


            queryBuilder.Remove(queryBuilder.Length - 1, 1);

            return queryBuilder.ToString();
        }

        protected string GetRequestUrl(bool secure)
        {
            return secure ? FacebookSecureRestUrl : FacebookRestUrl;
        }

        private static List<string> ConvertParameterDictionaryToList(IDictionary<string, string> parameterDictionary)
        {
            List<string> parameters = new List<string>();

            foreach (KeyValuePair<string, string> kvp in parameterDictionary)
            {
                parameters.Add(String.Format(CultureInfo.InvariantCulture, "{0}={1}", kvp.Key, kvp.Value));
            }
            return parameters;
        }

        private class FacebookComparer : IComparer<string>
        {
            #region IComparer<string> Members

            public int Compare(string x, string y)
            {
                //y is always greater than x, if its contained in it.
                //this puts image_1 ahead of image_1_link
                if (y.StartsWith(x))
                {
                    return -1;
                }
                else
                {
                    return x.CompareTo(y);
                }
            }

            #endregion
        }

        private string GenerateSignature(IDictionary<string, string> args)
        {
            return GenerateSignature(args, Secret);
        }

        public static string GenerateSignature(IDictionary<string, string> args, string secret)
        {
            SortedDictionary<string, string> sortedD = new SortedDictionary<string, string>(args, new FacebookComparer());
            List<string> argsAsKvpStrings = ConvertParameterDictionaryToList(sortedD);

            StringBuilder signatureBuilder = new StringBuilder();
            // Append all the parameters to the signature input paramaters
            foreach (string s in argsAsKvpStrings)
                signatureBuilder.Append(s);

            // Append the secret to the signature builder
            signatureBuilder.Append(secret);
            byte[] hash;

            MD5 md5 = MD5.Create();
            // Compute the MD5 hash of the signature builder
            hash = md5.ComputeHash(Encoding.UTF8.GetBytes(
                signatureBuilder.ToString()));

            // Reinitialize the signature builder to store the actual signature
            signatureBuilder = new StringBuilder();

            // Append the hash to the signature
            for (int i = 0; i < hash.Length; i++)
                signatureBuilder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));

            return signatureBuilder.ToString();
        }

        protected string BuildFqlQuery(IEnumerable<string> fieldsToLoad, string entity, string where, string orderBy)
        {
            if (string.IsNullOrEmpty(where))
                throw new ArgumentNullException("where");

            if (null == fieldsToLoad)
                throw new ArgumentNullException("fieldsToLoad");

            if (null == fieldsToLoad.FirstOrDefault())
                throw new ArgumentException("No fields specified to load");

            string fqlQuery = "select " +
                string.Join(",", fieldsToLoad.ToArray()) +
                " from " +
                entity +
                " where " +
                where;

            if (!string.IsNullOrEmpty(orderBy))
                fqlQuery = fqlQuery + " order by " + orderBy;

            return fqlQuery;
        }

        protected void SetupAsyncCompletion<TAsyncState, TResult>(IAsyncResult ar, Func<IAsyncResult, TResult> endOperation, EventHandler<AsyncCompletedEventArgs<TResult>> completedEvent)
            where TAsyncState : class, new()
        {
            var myAr = ar as LazyAsyncResult<TAsyncState>;
            var myOp = myAr.AsyncState as AsyncOperation;

            TResult result = default(TResult);
            Exception error = null;
            try
            {
                result = endOperation(ar);
            }
            catch (Exception ex)
            {
                error = ex;
            }

            myOp.PostOperationCompleted(
                (o) =>
                {
                    if (null != completedEvent)
                        completedEvent(this, (AsyncCompletedEventArgs<TResult>)o);
                },
                new AsyncCompletedEventArgs<TResult>(error, false, myOp.UserSuppliedState, result));
        }

        protected void SetupAsyncCompletion<TAsyncState>(IAsyncResult ar, AsyncCallback endOperation, EventHandler<AsyncCompletedEventArgs> completedEvent)
            where TAsyncState : class, new()
        {
            var myAr = ar as LazyAsyncResult<TAsyncState>;
            var myOp = myAr.AsyncState as AsyncOperation;

            Exception error = null;
            try
            {
                endOperation(ar);
            }
            catch (Exception ex)
            {
                error = ex;
            }

            myOp.PostOperationCompleted(
                (o) =>
                {
                    if (null != completedEvent)
                        completedEvent(this, (AsyncCompletedEventArgs)o);
                },
                new AsyncCompletedEventArgs(error, false, myOp.UserSuppliedState));
        }

        protected virtual void ObjectStateCallback(IAsyncResult ar)
        {
            var myAr = ar.AsyncState as LazyAsyncResult<object>;
            try
            {
                EndExecuteApiCall(ar);

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void BoolStateCallback(IAsyncResult ar)
        {
            LazyAsyncResult<BoolState> myAr = ar.AsyncState as LazyAsyncResult<BoolState>;
            try
            {
                XmlDocument resultDoc = EndExecuteApiCall(ar);
                myAr.MyAsyncState.Results = resultDoc.DocumentElement.InnerText == myAr.MyAsyncState.TrueResult;

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual AsyncCallback SafeCallback<ResultType>(Action<IAsyncResult, LazyAsyncResult<ResultType>> action) where ResultType : class, new()
        {
            return ar =>
            {
                var myAr = ar.AsyncState as LazyAsyncResult<ResultType>;
                try
                {
                    if (action != null)
                        action(ar, myAr);

                    if (!myAr.IsCompleted)
                        myAr.SetComplete();
                }
                catch (Exception ex)
                {
                    if (!myAr.IsCompleted)
                        myAr.SetComplete(ex);
                }
            };
        }

        protected virtual AsyncCallback SafeJsonDeserializeCallback<ResponseType, ResultType>(Func<ResponseType, ResultType> func) where ResultType : class, new()
        {
            return SafeCallback<ResultType>((ar, myAr) =>
            {
                ResponseType resp;
                using (var stm = EndGetQueryResponseStream(ar))
                {
                    string txt = null;
                    try
                    {
                        txt = new StreamReader(stm).ReadToEnd();
                        Debug.WriteLine(txt);
                        resp = new JavaScriptSerializer().Deserialize<ResponseType>(txt);
                    }
                    catch (InvalidOperationException)
                    {
                        if (txt == null)
                            throw;

                        ErrorCheck(new JavaScriptSerializer().Deserialize<Schema.ErrorResponse>(txt));

                        throw;
                    }
                }

                if (func != null || resp != null)
                {
                    myAr.MyAsyncState = func(resp);
                }
            });
        }

        protected virtual AsyncCallback SafeXmlDeserializeCallback<ResponseType, ResultType>(Func<ResponseType, ResultType> func) where ResultType : class, new()
        {
            return SafeCallback<ResultType>((ar, myAr) =>
            {
                ResponseType resp;
                using (var stm = EndGetQueryResponseStream(ar))
                {
                    try
                    {
#if DEBUG
                        string txt = new StreamReader(stm).ReadToEnd();
                        Debug.WriteLine(txt);
                        resp = (ResponseType)new XmlSerializer(typeof(ResponseType)).Deserialize(new StringReader(txt));
#else
                        resp = (ResponseType)new XmlSerializer(typeof(ResponseType)).Deserialize(stm);
#endif
                    }
                    catch (InvalidOperationException)
                    {
                        var ms = stm as MemoryStream;
                        if (ms == null)
                        {
                            throw;
                        }

                        ms.Seek(0, SeekOrigin.Begin);
                        XmlDocument doc = new XmlDocument();
                        doc.Load(ms);
                        ErrorCheck(doc);

                        throw;
                    }
                }

                if (func != null || resp != null)
                {
                    myAr.MyAsyncState = func(resp);
                }
            });
        }

        protected virtual void Int32StateCallback(IAsyncResult ar)
        {
            LazyAsyncResult<Int32State> myAr = ar.AsyncState as LazyAsyncResult<Int32State>;
            try
            {
                XmlDocument resultDoc = EndExecuteApiCall(ar);
                int result;
                Int32.TryParse(resultDoc.DocumentElement.InnerText, out result);
                myAr.MyAsyncState.Result = result;

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        protected virtual void Int64StateCallback(IAsyncResult ar)
        {
            var myAr = ar.AsyncState as LazyAsyncResult<Int64State>;
            try
            {
                XmlDocument resultDoc = EndExecuteApiCall(ar);
                long result;
                Int64.TryParse(resultDoc.DocumentElement.InnerText, out result);
                myAr.MyAsyncState.Result = result;

                if (!myAr.IsCompleted)
                    myAr.SetComplete();
            }
            catch (Exception ex)
            {
                if (!myAr.IsCompleted)
                    myAr.SetComplete(ex);
            }
        }

        #endregion

        #region Users
        public void IsAppUser(long uid, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginIsAppUser(
                uid,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndIsAppUser, OnIsAppUserCompleted); },
                op);
        }

        protected virtual void OnIsAppUserCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = IsAppUserCompleted;

            if (null != eh)
                eh(sender, e);
        }

        public IAsyncResult BeginIsAppUser(long uid, AsyncCallback cb, object state)
        {
            var args = new Dictionary<string, string>();
            args.Add("uid", uid.ToString());

            var myAr = new LazyAsyncResult<BoolState>(cb, state);

            BeginExecuteApiCall(@"users.isappuser", args, false, false, BoolStateCallback, myAr);
            return myAr;
        }

        public IAsyncResult BeginIsAppUser(AsyncCallback cb, object state)
        {
            AssertActiveSession();

            var args = new Dictionary<string, string>();
            var myAr = new LazyAsyncResult<BoolState>(cb, state);

            BeginExecuteApiCall(@"users.isappuser", args, true, false, BoolStateCallback, myAr);
            return myAr;
        }

        public bool EndIsAppUser(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }
        #endregion

        #region Pages

        public void IsFan(long uid, object userState)
        {
            AsyncOperation op = AsyncOperationManager.CreateOperation(userState);
            IAsyncResult opAr = BeginIsFan(
                uid,
                (ar) => { SetupAsyncCompletion<BoolState, bool>(ar, EndIsFan, OnIsFanCompleted); },
                op);
        }

        protected virtual void OnIsFanCompleted(object sender, AsyncCompletedEventArgs<bool> e)
        {
            var eh = IsFanCompleted;

            if (null != eh)
                eh(sender, e);
        }

        public IAsyncResult BeginIsFan(long pageId, AsyncCallback callback, object state)
        {
            AssertActiveSession();
            return BeginIsFan(pageId, default(long), callback, state);
        }

        public IAsyncResult BeginIsFan(long pageId, long uid, AsyncCallback callback, object state)
        {
            var args = new Dictionary<string, string>();
            args.Add("page_id", pageId.ToString());
            args.Add("uid", uid.ToString());

            var myAr = new LazyAsyncResult<BoolState>(callback, state);
            BeginExecuteApiCall(@"pages.isFan", args, uid == default(long), false, BoolStateCallback, myAr);
            return myAr;
        }

        public bool EndIsFan(IAsyncResult ar)
        {
            return ar.End<BoolState>().Results;
        }
        #endregion
    }
}
